10.5. 中斷驅(qū)動 I/O

2018-02-24 15:50 更新

10.5.?中斷驅(qū)動 I/O

無論何時一個數(shù)據(jù)傳送到或自被管理的硬件可能因為任何原因而延遲, 驅(qū)動編寫者應(yīng)當(dāng)實現(xiàn)緩存. 數(shù)據(jù)緩存幫助來分離數(shù)據(jù)傳送和接收從寫和讀系統(tǒng)調(diào)用, 并且整個系統(tǒng)性能受益.

一個好的緩存機制產(chǎn)生了中斷驅(qū)動的 I/O, 一個輸入緩存在中斷時填充并且被讀取設(shè)備的進(jìn)程清空; 一個輸出緩存由寫設(shè)備的進(jìn)程填充并且在中斷時清空. 一個中斷驅(qū)動的輸出的例子是 /dev/shortprint 的實現(xiàn).

為使中斷驅(qū)動的數(shù)據(jù)傳送成功發(fā)生, 硬件應(yīng)當(dāng)能夠產(chǎn)生中斷, 使用下列語義:

  • 對于輸入, 設(shè)備中斷處理器, 當(dāng)新數(shù)據(jù)到達(dá)時, 并且準(zhǔn)備好被系統(tǒng)處理器獲取. 進(jìn)行的實際動作依賴是否設(shè)備使用 I/O 端口, 內(nèi)存映射, 或者 DMA.

  • 對于輸出, 設(shè)備遞交一個中斷, 或者當(dāng)它準(zhǔn)備好接受新數(shù)據(jù), 或者確認(rèn)一個成功的數(shù)據(jù)傳送. 內(nèi)存映射的和能DMA的設(shè)備常常產(chǎn)生中斷來告訴系統(tǒng)它們完成了這個緩存.

在一個讀或?qū)懪c實際數(shù)據(jù)到達(dá)之間的時間關(guān)系在第 6 章的"阻塞和非阻塞操作"一節(jié)中介紹.

10.5.1.?一個寫緩存例子

我們已經(jīng)幾次提及 shortprint 驅(qū)動; 現(xiàn)在是時候真正看看. 這個模塊為并口實現(xiàn)一個非常簡單, 面向輸出的驅(qū)動; 它是足夠的, 但是, 來使能文件打印. 如果你選擇來測試這個驅(qū)動, 但是, 記住你必須傳遞給打印機一個文件以它理解的格式; 不是所有的打印機在給一個任意數(shù)據(jù)的流時很好響應(yīng).

shortprint 驅(qū)動維護(hù)一個一頁的環(huán)形輸出緩存. 當(dāng)一個用戶空間進(jìn)程寫數(shù)據(jù)到這個設(shè)備, 數(shù)據(jù)被填入緩存, 但是寫方法實際沒有進(jìn)行任何 I/O. 相反, shortp_write 的核心看來如此:


while (written < count)
{
        /* Hang out until some buffer space is available. */
        space = shortp_out_space();
        if (space <= 0) {
                if (wait_event_interruptible(shortp_out_queue,
                                             (space = shortp_out_space()) > 0))
                        goto out;
        }

        /* Move data into the buffer. */
        if ((space + written) > count)
                space = count - written;

        if (copy_from_user((char *) shortp_out_head, buf, space)) {
                up(&shortp_out_sem);
                return -EFAULT;
        }
        shortp_incr_out_bp(&shortp_out_head, space);
        buf += space;
        written += space;

        /* If no output is active, make it active. */
        spin_lock_irqsave(&shortp_out_lock, flags);
        if (! shortp_output_active)
                shortp_start_output();
        spin_unlock_irqrestore(&shortp_out_lock, flags);
}

out:
*f_pos += written;

一個旗標(biāo) ( shortp_out_sem ) 控制對這個環(huán)形緩存的存取; shortp_write 就在上面的代碼片段之前獲得這個旗標(biāo). 當(dāng)持有這個旗標(biāo), 它試圖輸入數(shù)據(jù)到這個環(huán)形緩存. 函數(shù) shortp_out_space 返回可用的連續(xù)空間的數(shù)量(因此, 沒有必要擔(dān)心緩存回繞); 如果這個量是 0, 驅(qū)動等到釋放一些空間. 它接著拷貝它能夠的數(shù)量的數(shù)據(jù)到緩存中.

一旦有數(shù)據(jù)輸出, shortp_write 必須確保數(shù)據(jù)被寫到設(shè)備. 數(shù)據(jù)的寫是通過一個工作隊列函數(shù)完成的; shortp_write 必須啟動這個函數(shù)如果它還未在運行. 在獲取了一個單獨的, 控制存取輸出緩存的消費者一側(cè)(包括 shortp_output_active)的數(shù)據(jù)的自旋鎖后, 它調(diào)用 shortp_start_output 如果需要. 接著只是注意多少數(shù)據(jù)被寫到緩存并且返回.

啟動輸出進(jìn)程的函數(shù)看來如下:


static void shortp_start_output(void)
{
        if (shortp_output_active) /* Should never happen */
                return;

        /* Set up our 'missed interrupt' timer */
        shortp_output_active = 1;
        shortp_timer.expires = jiffies + TIMEOUT;
        add_timer(&shortp_timer);

        /* And get the process going. */
        queue_work(shortp_workqueue, &shortp_work);
}

處理硬件的事實是, 你可以, 偶爾, 丟失來自設(shè)備的中斷. 當(dāng)發(fā)生這個, 你確實不想你的驅(qū)動一直停止直到系統(tǒng)重啟; 這不是一個用戶友好的做事方式. 最好是認(rèn)識到一個中斷已經(jīng)丟失, 收拾殘局, 繼續(xù). 為此, shortprint 甚至一個內(nèi)核定時器無論何時它輸出數(shù)據(jù)給設(shè)備. 如果時鐘超時, 我們可能丟失一個中斷. 我們很快會看到定時器函數(shù), 但是, 暫時, 讓我們堅持在主輸出功能上. 那是在我們的工作隊列函數(shù)里實現(xiàn)的, 它, 如同你上面看到的, 在這里被調(diào)度. 那個函數(shù)的核心看來如下:


spin_lock_irqsave(&shortp_out_lock, flags);
/* Have we written everything? */
if (shortp_out_head == shortp_out_tail)
{ /* empty */
        shortp_output_active = 0;
        wake_up_interruptible(&shortp_empty_queue);
        del_timer(&shortp_timer);

}
/* Nope, write another byte */
else
        shortp_do_write();
/* If somebody's waiting, maybe wake them up. */
if (((PAGE_SIZE + shortp_out_tail -shortp_out_head) % PAGE_SIZE) > SP_MIN_SPACE)
{
        wake_up_interruptible(&shortp_out_queue);
}
spin_unlock_irqrestore(&shortp_out_lock, flags);

因為我們在使用共享變量的輸出一側(cè), 我們必須獲得自旋鎖. 接著我們看是否有更多的數(shù)據(jù)要發(fā)送; 如果無, 我們注意輸出不再激活, 刪除定時器, 并且喚醒任何在等待隊列全空的進(jìn)程(這種等待當(dāng)設(shè)備被關(guān)閉時結(jié)束). 如果, 相反, 有數(shù)據(jù)要寫, 我們調(diào)用 shortp_do_write 來實際發(fā)送一個字節(jié)到硬件.

接著, 因為我們可能在輸出緩存中有空閑空間, 我們考慮喚醒任何等待增加更多數(shù)據(jù)給那個緩存的進(jìn)程. 但是我們不是無條件進(jìn)行喚醒; 相反, 我們等到有一個最低數(shù)量的空間. 每次我們從緩存拿出一個字節(jié)就喚醒一個寫者是無意義的; 喚醒進(jìn)程的代價, 調(diào)度它運行, 并且使它重回睡眠, 太高了. 相反, 我們應(yīng)當(dāng)?shù)鹊竭M(jìn)程能夠立刻移動相當(dāng)數(shù)量的數(shù)據(jù)到緩存. 這個技術(shù)在緩存的, 中斷驅(qū)動的驅(qū)動中是普通的.

為完整起見, 這是實際寫數(shù)據(jù)到端口的代碼:


static void shortp_do_write(void)
{
        unsigned char cr = inb(shortp_base + SP_CONTROL);
        /* Something happened; reset the timer */
        mod_timer(&shortp_timer, jiffies + TIMEOUT);
        /* Strobe a byte out to the device */
        outb_p(*shortp_out_tail, shortp_base+SP_DATA);
        shortp_incr_out_bp(&shortp_out_tail, 1);
        if (shortp_delay)
                udelay(shortp_delay);
        outb_p(cr | SP_CR_STROBE, shortp_base+SP_CONTROL);
        if (shortp_delay)
                udelay(shortp_delay);
        outb_p(cr & ~SP_CR_STROBE, shortp_base+SP_CONTROL);
}

這里, 我們復(fù)位定時器來反映一個事實, 我們已經(jīng)作了一些處理, 輸送字節(jié)到設(shè)備, 并且更新了環(huán)形緩存指針.

工作隊列函數(shù)沒有直接重新提交它自己, 因此只有一個單個字節(jié)會被寫入設(shè)備. 在某一處, 打印機將, 以它的緩慢方式, 消耗這個字節(jié)并且準(zhǔn)備好下一個; 它將接著中斷處理器. shortprint 中使用的中斷處理是簡短的:


static irqreturn_t shortp_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
        if (! shortp_output_active)
                return IRQ_NONE;
        /* Remember the time, and farm off the rest to the workqueue function */
        do_gettimeofday(&shortp_tv);
        queue_work(shortp_workqueue, &shortp_work);
        return IRQ_HANDLED;
}

因為并口不要求一個明顯的中斷確認(rèn), 中斷處理所有真正需要做的是告知內(nèi)核來再次運行工作隊列函數(shù).

如果中斷永遠(yuǎn)不來如何? 至此我們已見到的驅(qū)動代碼將簡單地停止. 為避免發(fā)生這個, 我們設(shè)置了一個定時器在幾頁前. 當(dāng)定時器超時運行的函數(shù)是:


static void shortp_timeout(unsigned long unused) 
{
        unsigned long flags;
        unsigned char status;
        if (! shortp_output_active)
                return;
        spin_lock_irqsave(&shortp_out_lock, flags);
        status = inb(shortp_base + SP_STATUS);

        /* If the printer is still busy we just reset the timer */
        if ((status & SP_SR_BUSY) == 0 || (status & SP_SR_ACK)) {

                shortp_timer.expires = jiffies + TIMEOUT;
                add_timer(&shortp_timer);
                spin_unlock_irqrestore(&shortp_out_lock, flags);
                return;
        }
        /* Otherwise we must have dropped an interrupt. */
        spin_unlock_irqrestore(&shortp_out_lock, flags);
        shortp_interrupt(shortp_irq, NULL, NULL);
}

如果沒有輸出要被激活, 定時器函數(shù)簡單地返回. 這避免了定時器重新提交自己, 當(dāng)事情在被關(guān)閉時. 接著, 在獲得了鎖之后, 我們查詢端口的狀態(tài); 如果它聲稱忙, 它完全還沒有時間來中斷我們, 因此我們復(fù)位定時器并且返回. 打印機能夠, 有時, 花很長時間來使自己準(zhǔn)備; 考慮一下缺紙的打印機, 而每個人在一個長周末都不在. 在這種情況下, 只有耐心等待直到事情改變.

但是, 如果打印機聲稱準(zhǔn)備好了, 我們一定丟失了它的中斷. 這個情況下, 我們簡單地手動調(diào)用我們的中斷處理來使輸出處理再動起來.

shortpirnt 驅(qū)動不支持從端口讀數(shù)據(jù); 相反, 它象 shortint 并且返回中斷時間信息. 但是一個中斷驅(qū)動的讀方法的實現(xiàn)可能非常類似我們已經(jīng)見到的. 從設(shè)備來的數(shù)據(jù)可能被讀入驅(qū)動緩存; 它可能被拷貝到用戶空間只在緩存中已經(jīng)累積了相當(dāng)數(shù)量的數(shù)據(jù), 完整的讀請求已被滿足, 或者某種超時發(fā)生.

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號