iT邦幫忙

2022 iThome 鐵人賽

DAY 14
0

Synchronize Exception (同步異常)

Exception 意思為處理器在執行指令時,遇到了非預期的情況,就稱之為 Exception。

Synchronize Exception 常見的發生為 software Interrupt,當程式中發生除以 0,或是 page fault,System call,等等,皆會發生 Exception,Exception 發生時,process 會先暫停,接著執行中斷處理流程服務 (interrupt service routine, aka ISR or interrupt handler),在 xv6 中會以 trap 的行為來代稱 ISR。trap 會決定該如何處理 Exception (讀取 sscause CSR)。

Asynchronous Exception (異步異常)

如果發生了 Asynchronous Exception (在 xv6 中皆以 trap 代稱),會發生的一連串行為,以及 trap 的 handler code 是如何執行的,而下面將針對 Asynchronous Exception 進行討論。

Asynchronous Exception 可以分成兩種,一種是讀寫記憶體發生錯誤所造成,另外一種為外部 I/O 設備所發起的 Interrupt,Interrupt 為當 process 或是 event 需要立刻被作業系統關注時,由軟體或是硬體所發出的訊號。

Interrupt 可以分成外部 Interrupt (由外部設備所產生的 Interrupt,稱為 signal) 以及內部的 Interrupt (由軟體所產生的,通常稱為 trap,xv6 將 signal 和 trap 皆稱為 trap),我們前面提到了幾種 Interrupt,包含 device Interrupt, software Interrupt, timer Interrupt,這一些 Interrupt 只是先帶過而已,下面將先針對 device Interrupt 進行討論。

(關於 Exception 與 Interrupt,各地方說明皆不同,6S801 中是將 trap 分為兩種,一種為 Exception,另外一種為 Interrupt,因為 Exception 和 Interrupt 的處理皆會發生 trap。而在一些網站中,將 Exception 分成四種,分別為 trap, Interrupt, page fault, abort,而在 OSDev 中是將 Interrupt 分成 Exceptions, Hardware Interrupt, 和 Software Interrupt。這邊先以 6S081 中提及的概念為主。)

Exception, Interrupt, Trap, Trap Handler in xv6

整理起來,在 xv6 中,Exception 分為 Synchronize Exception (會由 software Interrupt 所觸發) 和 Asynchronous Exception,而 Asynchronous Exception 可以分成 Externel Interrupt 和 Internel Interrupt,而 Interrupt 的發生會伴隨著特權模式的切換,從 user mode 切換到 supervisor mode 或是 kernel space 底下發生的 Interrupt,這個情況稱之為 trap,而處理 trap 的程式碼,也就是處理特權模式切換的程式碼,又稱作為 trap handler。

Device Interrupt

如果網卡收到了一個封包,會產生 Interrupt,如果鍵盤收到一個按鍵,也會產生出 Interrupt。Interrupt 發生時會將控制權轉交到作業系統中,整個 Interrupt 的處理就是儲存目前的 process 或是目前工作的狀態,包含暫存器等等 (在 trap 的介紹中會看到詳細的處理流程)。而硬體產生的 Interrupt 處理流程其實看 System call, page fault 都使用了類似的處理機制,但 Interrupt 和 System call 的 Interrupt 有一些不同。

  • Synchronize Interrupt (同步中斷):
    如果一個 process 呼叫了 System call,會停下目前的 process,接著進入到作業系統中,在作業系統工作完成之後回復 process 的狀態並繼續執行。這樣的 Interrupt 稱為 Synchronize Interrupt (同步中斷)。
  • Asynchronous Interrupt (異步中斷):
    而在硬體中,當產生出了 Interrupt,由於和 process 沒有關聯,因此 process 會繼續執行,同時作業系統也會進行對應的 Interrupt handler。process 沒有停下來,這是屬於 Asynchronous Interrupt (異步中斷)。
  • Concurrency (並行): 上面說到設備產生了 Interrupt,而設備和 CPU 此時還是一同在執行中的。網卡自己處理來自網路的封包,然後某個時間產生出 Interrupt,但同時 CPU 還在繼續執行,我們需要一些機制處理這裡發生的 Concurrency。

而外部設備產生的 Interrupt,我們可以先觀察一下 SiFive 主機板 (與 QEMU 模擬的類似)

source
可以看到許多外部裝置,包含 RJ45, micor USB 等等,而正中間為代號 U540 的 SOC,我們可以試著看到這顆 SOC 的架構圖

source
可以看帶這一些外部設備,包含 UART, GPIO 等等,都是連接在處理器上,而我們再試著深入下去

source
可以看到有兩個控制器,分別為 PLIC 和 CLINT,而根據上圖,我們可以知道以下兩件事情

  • PLIC (Platform-Level Interrupt Controller) : 為在 RISC-V 系統中的全域中斷控制器 (global interrupt controller)。處理 machine mode 和 Uupervisor mode 的 External Interrupt。
  • CLINT (Core Local Interruptor): 處理 Mahcine mode 的 Software Interrupt 和 Timer Interrupt。CLINT 會產生出 Software Interrupt 和 Timer Interrupt,每一個 hart 皆有。
  • Hart : 在前面我們將其與 thread 混用,Hart 為 Hardware thread的簡寫。

而 PLIC 旁邊的 53 表示我們有 53 個來自不同設備的 Interrupt。這一些 Interrupt 到達 PLIC,PLIC 連接到某一個 hart,如果該 hart 正在處理 Interrupt,則 PLIC 會先保留 Interrupt,直到 CPU 的 hart 可以處理這個 Interrupt。

而在 SiFive FU540-C000 Manual: v1p0 文件中,有詳細的說明整個 Interrupt 的處理,大致上整理如下

  • hart 通過讀取 claim/complete 暫存器 (該暫存器位於 PLIC 中) 去執行 interrupt claim,claim/complete 暫存器會回傳擁有最高優先級的 Interrupt 的 ID,如果都沒有等待被處理 (Pending) 的 Interrupt 則會回傳 0。
  • 如果成功獲得了最高優先級的 Interrupt ID 並成功執行 Interrupt claim,會自動將該 Interrupt 來源裝置的等待處理 Interrupt 的 bit field 清除(在暫存器中)。這樣 PLIC 就不會將該 Interrupt 給其他的 hart 處理了。
  • hart 處理完 Interrupt 後,會通知 PLIC。
  • PLIC 清除 Interrupt 的訊息。

Driver (驅動程式)

上面我們看到有許多的外部裝置會導致外部中斷的發生,而這一些外部裝置也會有一些程式碼對這一些裝置進行管理,管理這一些裝置的程式碼我們就稱為驅動程式,常常又稱為 Driver。

Driver 為讓作業系統與裝置能夠相互溝通的軟體。

下面我們看到一般化的外部裝置。

  • interface : 暴露在外,提供給作業系統使用的一些 API 以及暫存器,或是一些協定等等。
  • internel : 裡面包含暴露在外 API 的實作,微處理器,RAM,ROM,或是一些與裝置相關的芯片 (芯片中會有韌體來實現芯片該有的功能,像是 RAID 芯片)。

而 UART 為 RISC-V 中的外部裝置,我們可以試著看看 UART 的驅動程式,UART 的驅動程式位於 uart.c 中,驅動程式被分成 bottom 以及 top 這兩個部分。

bottom

bottom 通常為 Interrupt Handler。當一個 Interrupt 通過 PLIC 送到 CPU,CPU 接受了這個 Interrupt,CPU 會呼叫對應的 Interrupt handler (這個 Interrupt handler 並不存在於 process 的 context 中或是 trapframe 中)。

top

top 部分提供了一些給 user space 或是 kernel space 中程式所使用的 API,以 UART 來說,提供了 write()read() 這兩個 API。

編寫 Driver 通常會需要 memory mapped I/O 來完成,得到對應裝置的物理記憶體地址,通過讀寫該物理記憶體地址對應到的 CSR (使用load/store 等等...) 來操作外部裝置。在 SiFive FU540-C000 Manual: v1p0 文件中,UART 對應到的物理記憶體地址為 0x1001000。

不過在 xv6 並非如此 (xv6 模擬類似於 SiFive FU540-C000 的硬體),可以通過 memory_layout.h 得知 UART 對應的物理記憶體地址。下面為 UART 的 memory map。

可以將資料寫入到 txdata 暫存器中,通過 Serial port 一個一個 bit 的發送資料,發送到連接到的另外一個 UART,另一個 UART 能夠將多個 bit 組成一個 byte。

而上面這一些暫存器在文檔中也可以看到這一些暫存器中對應 field 的意義。

通常在作業系統中有許多驅動程式 (對應到不同的裝置)。

xv6 External Interrupt set (UART)

當 xv6 啟動時,我們可以看到 console 上面有一個 $ 號,這個 $ 號實際上會傳送到 UART 的暫存器,UART 將該字元發送之後產生出一個 Interrupt,QEMU 中會模擬另外一端也有一個 UART,接收剛剛傳送的 $,這個 UART 連接到了 console,因此會在 console 上印出 $

而我們在前面 xv6 的啟動與架構中看到,main()函式的第一行程式碼即為consoleinit(),我們可以試著進行追蹤,了解 UART 是如何產生出 Interrupt,以及外部裝置的 Interrupt 是如何啟用與設置的。下面將先看過與 Interrupt 的暫存器,而在 trap 篇章中提及的部分將會快速的帶過,主要將聚焦於設置外部中斷的相關暫存器設置。

sip (Supervisor Pending Register)


當發生 Interrupt 時,處理器可以通過這個暫存器知道目前發生的是什麼類型的 Interrupt。總共有三種 Interrupt,分別為 Software Interrupt, Timer Interrupt, External Interrupt。

hart 通過將 SSIP (Supervisor Mode Software Interrupt Pending) 域設置成 1 來等待處理 (pending) Supervisor Mode Software Interrupt,如果設置成 0 表示清除 Supervisor Mode Software Interrupt。而 USIP (User Mode Software Interrupt) 也是相同的概念。

STIP 則表示 Supervisor Mode Timer Interrupt Pending,SEIP 表示 Supervisor Mode External Interrupt Pending。

sie (Supervisor Interrupt Enable Register)


如同 sip,也同樣得對應到 Software Interrupt, Timer Interrupt, External Interrupt。有相對應的域,UEIE 表示 User Mode External Interrupt Enable。

而在 start.c 中,可以看到通過設置 SIE 暫存器,去啟用 External Interrupt,如下 (位於第 20 行處)。

void
start()
{
  // set M Previous Privilege mode to Supervisor, for mret.
  unsigned long x = r_mstatus();
  x &= ~MSTATUS_MPP_MASK;
  x |= MSTATUS_MPP_S;
  w_mstatus(x);

  // set M Exception Program Counter to main, for mret.
  // requires gcc -mcmodel=medany
  w_mepc((uint64)main);

  // disable paging for now.
  w_satp(0);

  // delegate all interrupts and exceptions to supervisor mode.
  w_medeleg(0xffff);
  w_mideleg(0xffff);
  w_sie(r_sie() | SIE_SEIE | SIE_STIE | SIE_SSIE);

  // configure Physical Memory Protection to give supervisor mode
  // access to all of physical memory.
  w_pmpaddr0(0x3fffffffffffffull);
  w_pmpcfg0(0xf);

  // ask for clock interrupts.
  timerinit();

  // keep each CPU's hartid in its tp register, for cpuid().
  int id = r_mhartid();
  w_tp(id);

  // switch to supervisor mode and jump to main().
  asm volatile("mret");
}

接著 mret 會切換到 supervisor mode 並進入到 main,如同xv6 啟動與架構篇章中所提及,而在 main 當中,會先執行 consoleinit()

void
consoleinit(void)
{
  initlock(&cons.lock, "cons");

  uartinit();

  // connect read and write system calls
  // to consoleread and consolewrite.
  devsw[CONSOLE].read = consoleread;
  devsw[CONSOLE].write = consolewrite;
}

初始化 lock 之後對 UART 進行初始化。

void
uartinit(void)
{
  // disable interrupts.
  WriteReg(IER, 0x00);

  // special mode to set baud rate.
  WriteReg(LCR, LCR_BAUD_LATCH);

  // LSB for baud rate of 38.4K.
  WriteReg(0, 0x03);

  // MSB for baud rate of 38.4K.
  WriteReg(1, 0x00);

  // leave set-baud mode,
  // and set word length to 8 bits, no parity.
  WriteReg(LCR, LCR_EIGHT_BITS);

  // reset and enable FIFOs.
  WriteReg(FCR, FCR_FIFO_ENABLE | FCR_FIFO_CLEAR);

  // enable transmit and receive interrupts.
  WriteReg(IER, IER_TX_ENABLE | IER_RX_ENABLE);

  initlock(&uart_tx_lock, "uart");
}

首先先關閉中斷,接著設定 baud rate 以及設定一個 word 為 8 bit,也就是 1 byte。在經過上面這一些設定之後,UART 便可以產生 Interrupt 了,但是正如上面所提及,Externel Interrupt 需要經過 PLIC 接著到 CPU,我們需要對 PLIC 進行一些操作,才能使得 Externel Interrupt 被 CPU 所處理。

void
plicinit(void)
{
  // set desired IRQ priorities non-zero (otherwise disabled).
  *(uint32*)(PLIC + UART0_IRQ*4) = 1;
  *(uint32*)(PLIC + VIRTIO0_IRQ*4) = 1;
}

PLIC 如同 UART 也會在 0x8000000 以下的物理記憶體地址佔有一定的空間,讓我們能夠通過這一個物理記憶體地址對外部設備進行存取,而第 1 行 1 表示 PLIC 會接收來自於 UART 的 Interrupt,而接著 PLIC 會將 Interrupt 送到 hart 進行處理。而下面我們就需要對每一個 hart 進行一些對於 PLIC 的初始化設置。

void
plicinithart(void)
{
  int hart = cpuid();
  
  // set enable bits for this hart's S-mode
  // for the uart and virtio disk.
  *(uint32*)PLIC_SENABLE(hart) = (1 << UART0_IRQ) | (1 << VIRTIO0_IRQ);

  // set this hart's S-mode priority threshold to 0.
  *(uint32*)PLIC_SPRIORITY(hart) = 0;
}

可以看到我們先通過 cpuid() 取得 hartid,而該 hart 會設置對於 UART0 以及 VIRTIO0 的中斷處理 IRQ 的優先級為 0。

到這邊完成了 UART 產生 Interrupt,而 Interrupt 能夠通過 PLIC 傳送到 CPU 的 hart,接著需要在 hart 對 sstatus 進行一些設置,表示準備接收 Interrupt。在 scheduler(後面將介紹) 存在一個 intr_on() 的函式。

static inline void
intr_on()
{
  w_sstatus(r_sstatus() | SSTATUS_SIE);
}

可以看到這邊設置了 sstatus 的 SIE 域,而我們看到 sstatus CSR 的結構。

將 SIE 域設置為 1 表示啟用 Supervisor mode Interrupt (Enable / Disable),到這裡 hart 便可以接收 Interrupt 了。而如果這時候 PLIC 有 pending 中的 Interrupt,便會送到 CPU hart 接收 Interrupt。

source : https://mit-public-courses-cn-translatio.gitbook.io/mit6-s081/lec09-interrupts/9.4-xv6-set-interrupt

reference

xv6-riscv
Operating System Concepts, 9/e
RISC-V xv6 Book
從 RISC-V 處理器到 UNIX 作業系統
1.6 Exceptions, Traps, and Interrupts


上一篇
Day-12 C Memory Management, Process Memory layout in UNIX
下一篇
Day-14 xv6 Trap (user mode): overview, ecall
系列文
與作業系統的第一類接觸 : 探索 xv631
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言