1. 程式人生 > 其它 >6.s081 中斷

6.s081 中斷

中斷和裝置驅動

產生中斷的硬體

中斷是trap的一種, 當裝置需要得到OS注意就會發出中斷並呼叫驅動的中斷處理器(devintr函式).

中斷與其它型別的trap不同點:

  1. asynchronous. 中斷處理器與當前執行的程序在CPU上沒有關聯.
  2. concurrency. CPU和生成中斷的裝置並行執行.
  3. program device. 產生中斷的裝置需要被程式設計(每個裝置都有一個程式設計手冊), 包含裝置有什麼樣的暫存器, 可以執行什麼操作等.

本節課主要關注外部裝置的中斷. 外設中斷來自主機板上的裝置. 例如UART(UART會連線到電腦和顯示器), 該裝置對映到實體地址0x10000000(UART0

). 在0x80000000以上的地址空間對映著實體記憶體.

所有裝置連線在處理器上, 處理器通過PLIC(platform level interrupt control)管理外設的中斷.

左上角有53個來自裝置的中斷, 這些中斷到達PLIC之後, PLIC路由這些中斷到右邊的CPU核. 如果所有核都在忙, 就保留中斷, 直到有CPU核忙完來處理中斷.

  • PLIC通知有一個帶處理的中斷.
  • 其中一個CPU核接收.
  • CPu核處理完後通知PLIC.
  • PLIC不再儲存中斷資訊.

裝置驅動

驅動: os中的程式碼, 管理一個特定的裝置(配置裝置硬體, 告訴裝置執行哪些操作, 處理中斷結果, 和等待裝置完成的程序互動). 驅動分成兩個部分: bottom/top.

  • bottom: 在中斷時執行, 通常是interrupt handler. 當中斷傳送到CPU, CPU接收這個中斷, 會呼叫interrupt handler來處理中斷(不執行在程序的上下文中). 由於不執行在程序的context, 程序的page table不知道向哪個地址讀寫資料.
  • top: 在程序的核心執行緒執行. 通過像read/write這種系統呼叫被呼叫. top會要求硬體開始操作, 然後等待操作結束. 當裝置操作結束後產生中斷, interrupt handler就會喚醒等待中的程序然後告訴裝置進行下一輪工作.

驅動中有一些buffer, top程式碼可以從buffer讀寫資料, interrupt handler也可以向buffer讀寫資料. 從而將裝置和CPU解耦開.

對裝置的程式設計是通過memory mapped I/O完成. 裝置的地址在實體地址的固定處, 從而os知道這些位置, 然後通過load/store(控制暫存器)對這些地址程式設計.

UART是16550型號, 通過這個裝置來與鍵盤和console互動.

  • 控制暫存器000(RHRTHR), 如果寫他會將資料寫入暫存器中並傳輸到其他地方, 如果讀它就可以讀出儲存在暫存器中的內容, UART可以通過串列埠傳送資料bit, 線上路另一端會有另一個UART, 能將bit組合成一個byte. 每次暫存器中的字元被讀取, UART就會從buffer中刪除並且當buffer為空時, 清除LSR中的ready位. 同理通過load將資料寫入這個暫存器中, UART會通過串列埠線將這個byte送出, 送出後, UART會發送中斷給核心, 這時候才能再次寫入下一個資料.
  • 控制暫存器001(IER), 通過它來控制UART是否產生中斷. 一個暫存器中的每一bit都有不同作用. IER中的bit0-bit3分別控制了不同的中斷.
  • 控制暫存器010(ISR), 包含幾個位來表明中斷狀態. 是否輸入的字元等待被程序讀取.

設定中斷

xv6啟動時, shell會輸出“$ “, 當我們在鍵盤上輸入ls, 最終可以看到“$ ls”. “$ ”是shell程式的輸出, “ls”是鍵盤輸入後顯示出來的.

  • 對於“$ ”, 裝置將字元傳輸給UART的暫存器, UART在傳送後生成一箇中斷, 在傳送的另一端也有一個UART, 這個UART連線console, 會進一步將“$ ”顯示在console上.
  • 對於“ls”, 鍵盤連線到UART輸入, 當按下一個鍵, UART會將按鍵字元通過序列口傳送給另一端的UART, 另一端的UART會將bit組合成一個byte, 再產生一箇中斷, 並告訴處理器有一個來自鍵盤的字元, 之後interrupt handler會處理這個字元.

中斷相關暫存器:

  • SIE(supervisor interrupt enable). E位專門針對外設的中斷, S位專門針對軟體中斷, T位專門針對定時器的中斷.

  • SSTATUS(supervisor status). 有一個bit來開啟或關閉中斷. 每個CPU核有獨立的SIE和SSTATUS.

  • SIP(supervisor interrupt pending). 可以檢視發生的中斷是什麼型別的.

  • SCAUSE.表明中斷的原因.

  • STVEC. 儲存trap時CPU執行使用者程式的pc, 為之後恢復程式執行做準備.

start函式將中斷和異常設定到supervisor mode, 然後設定SIE來接收中斷(E, S和T), 最後初始化定時器.

main函式呼叫一系列函式初始化.

先呼叫consoleinit來初始化UART. consoleinit呼叫uartinit.

uartinit配置了UART晶片. 關閉中斷, 設定字元長度為8bit重置FIFO, 最後再開啟中斷.

main中還會呼叫plicinitplicinithart, PLIC實體地址為0xC0000000. plicinit由0號CPU執行, 之後每個CPU核都需要呼叫plicinithart來表明對哪些中斷感興趣.

這時配置好了外部裝置, 並有PLIC來傳遞中斷. 接下來需要設定CPU來接收中斷. scheduler會排程程序, 在執行程序前會執行intr_on來使CPU接收中斷(設定SSTATUS, 開啟中斷標誌位).

UART驅動的top部分

shell輸出“$ ”到console. 在init.cmain函式, 是啟動後的第一個程序.

先通過mknod建立了console裝置, 之後通過dup建立stdoutstderr. 最終檔案描述符0, 1, 2都代表console. 然後執行sh程式. 之後shell向檔案描述符列印“$ ”

由於裝置是由檔案來表示的, 所以shell並不知道2對應了什麼. shell輸出的每個字元都會觸發write系統呼叫, wirte系統呼叫會走到sysfile.c中的sys_write.

sys_write呼叫filewrite, filewrite會先判斷檔案描述符的型別, 當為FD_DEVICE時, 會為特定裝置執行write函式, 當前裝置為console, 所以呼叫console.cconsolewrite函式.

consolewrite通過either_copyin將字元拷入, 在呼叫uartputc函式.

uartputc將字元寫入UART裝置, 所以consolewrite可以看成是UART的top部分. UART內部有一個buffer, 大小為32個字元, 還有一個為consumer提供的讀指標和一個為producer提供的寫指標, buffer是環形的.

shell是producer, 所以呼叫uartputc函式. 函式先判定buffer是否滿, 滿了就sleep, 使cpu出讓. 否則就將字元寫入buffer, 更新指標, 再呼叫uartstart.

uartstart先檢查當前裝置是否空閒並且buffer非空, 如果是, 就從buffer中讀出資料寫入THR傳送暫存器告訴裝置需要傳送位元組. 然後返回到shell(與trap一樣). 這樣UART可以同時將資料發出, 並中斷.

UART驅動的bottom部分

假設鍵盤生成了一箇中斷髮送給PLIC, PLIC路由給CPU, 那麼:

  1. 清除SIE的bit(阻止CPU核被其他中斷打擾), 處理完後在恢復SIE相應bit.

  2. 設定SEPC為當前pc.

  3. 儲存當前mode.

  4. 將mode設為supervisor mode.

  5. pc設為STVEC的值(trap處理程式地址). 當中斷時程式執行在使用者空間是, STVEC儲存的是uservec函式地址, 當執行在核心空間時, 儲存著kernelvec函式地址.

  6. uservec會呼叫usertrap函式. usertrap呼叫devintr.

  7. devintr先通過SCAUSE暫存器判定單曲是否是外設中斷. 如果是的話呼叫plic_claim來獲取中斷.

  8. plic_claim會找到CPU核來處理中斷並返回中斷號.

  9. 然後devintr通過返回的中斷號來判斷是否是UART中斷, 如果是就呼叫uartintr.

  10. uartintr會從UART的接收暫存器讀取資料, 之後將獲取到的資料傳遞給consoleintr函式. 當前為空. 所以執行uartstart函式.

  11. uartstart會將shell儲存在buffer的字元輸出(“$”).

  12. 這樣top和bottom就解耦開了.

併發

中斷的併發包括:

  • 裝置和CPU是並行的. 當UART向console傳送字元是, CPU會返回到shell, 但shell可能會執行系統呼叫, 向buffer中再寫入字元, 這叫做producer-consumer並行.
  • 中斷會停止當前執行的程式. 使用者程式沒問題, 之後從中斷返回後會繼續執行停止的指令. 但核心也被中斷打斷, 對於一些程式碼, 如果不能在執行期間被中斷, 核心需要臨時關閉中斷來保證程式碼的原子性.
  • 驅動的top和bottom是並行執行的. 可能會在某一時刻有兩個核同時對buffer進行讀寫操作.

中斷的演進

由於中斷相對於處理器來說很慢, 所以在產生中斷之前, 裝置上會執行大量的操作(非同步).

  • 對於快裝置, 產生中斷的頻率非常高, 超過了CPU的處理能力. 可以使用polling, CPU一直讀取外設的控制暫存器來檢查是否有資料, CPU不停輪詢裝置直到裝置有了資料.
  • 對於慢裝置, 這種方法浪費了CPU cycles, 因為在CPU輪詢裝置時, 沒有用CPU執行任何程式, 所以可以在裝置沒有資料時切換出來執行一些其他程式.