天天看點

程序以下的那些事兒程序以下的那些事兒

程序以下的那些事兒

硬體驅動程式的最底層

硬體驅動程式的最底層是什麼?是直接操作硬體的部分。直接與硬體互動的方法有2大類:

  • CPU主動發起,讀寫硬體的寄存器。這裡面又分為2種,PIO和MMIO。
    • PIO的方式在x86彙編語言裡,就是用io和out彙編語句,讀寫硬體控制器上的寄存器。
    • MMIO就是硬體會把自己的寄存器映射到主機的記憶體空間。也就是說,在程式員看來,通路MMIO寄存器就好像通路普通記憶體一樣,可以直接用彙編mov語句操作。在C語言可以用聲明為volatile的指針,然後直接用指派語句就可以讀寫硬體寄存器。
    • 另外,還有一種是DMA。但它不是獨立的,還是要先通過上述2種方法之一,CPU向硬體發出指令。接下來的過程才是,硬體自己讀寫指定的記憶體區域。讀寫過程不需要CPU參與。
  • 另外一種方式是IRQ。它是由硬體主動發起,打斷CPU正在執行的程式。CPU會跳到IRQ号對應的中斷号所對應的中斷服務程式。執行完中斷服務程式再傳回到原來的程式。

是以,驅動程式往往包括這麼幾個部分:初始化(設定硬體狀态,安裝中斷服務例程等等)、讀操作、寫操作、中斷服務例程。

中斷服務例程

中斷服務例程(ISR)如果什麼都不做,直接IRET,就可以傳回到被打斷的程式。(x86有一些中斷會有error code,要先彈出error code,這樣ESP才會指向傳回位址。)

問題是ISR不能什麼都不做啊,還是要做事的。這就需要儲存原程序的上下文,做完事後,恢複上下文,然後iret,原來的程序才能繼續執行。儲存和恢複上下文是作業系統要做的事(硬體也會做一部分,寫OS的人要搞清楚硬體做了什麼,還缺什麼)

程序的上下文其實就是程式(CS:EIP)、堆棧(ESP)、資料(DS),還有目前的CPU狀态(從軟體開發者角度看,主要是通用寄存器的值。其他的硬體負責儲存和恢複)。如果有分頁,就還涉及到頁表的切換。

IRET本身隻能負責恢複CS和EIP,而且前提是執行IRET前,ESP必須指向傳回位址。

系統調用

作業系統給應用程式提供功能,也是通過中斷服務例程。比如linux的系統調用。就是應用程式執行INT 80h。CPU就會跳到80号中斷服務例程。這個ISR根據eax的值,決定具體調用哪個c函數(sys_open、sys_read、sys_write等等)。

stackoverflow: int 80h已經過時了

并發

假如沒有中斷,就沒有并發了。

因為沒有其他辦法可以讓正在執行的程序停下來,除非他自己願意。

而且程序他自己願意停下來,也是用INT指令,觸發軟中斷(系統調用system call)。機制和硬體中斷類似。

是以,沒有中斷,就沒有并發。

時鐘

怎麼讓程序在非自願的情況下停下來?要用到時鐘中斷。可程式設計時鐘(Intel 8253 - Programmable Interval Timer)是每個PC上都有硬體,作業系統可以設定讓時鐘定時發出中斷。作業系統在時鐘中斷的服務例程裡,就可以切換程序。

多核

多核處理器每個核(或者說每個hyperthread)都有自己的APIC timer。

硬體中斷可以路由到指定的一個或多個核,其他核不受影響。

critical section、關中斷、鎖

如果程式要進入critical section,它不希望被非自願地打斷,他可以關中斷。關中斷這隻能OS使用,不可以給普通程序用,否則OS怎麼保持控制權?關中斷隻能關1個核的中斷,其他核仍然在運作。

linux早期曾經用軟體實作過關所有cpu的中斷。後來發現這個很慢,就廢棄了。多核同步改用鎖來實作。

所有鎖的實作基礎都是spinlock。需要硬體提供支援,比如使用x86的xchg指令。

(待續)

補充:

  • Minix2和Linux其實隻有一個核心棧,xv6是每個程序各自有一個核心棧。但是并沒有本質不同,對于各個程序的核心态來說,大家還是井水不犯河水,仿佛核心棧是自己的。

    核心執行的時候會被打斷,或者會阻塞。它的context也是儲存在這個棧上。

  • IRET有時候也會切換堆棧指針(ESP)——就是如果中斷發生前,運作的是使用者程序,那麼就涉及到特權級别轉換。從使用者态,切換到核心态。為了更安全,防止使用者堆棧溢出導緻系統崩潰。x86的CPU的硬體設計者,就這麼設計:另外設定一個棧來存放使用者程序的context和傳回位址,由硬體來保證這個堆棧空間是夠的。這個棧由TSS指定。
  • 是以,Minix2在切換上下文的時候,有時就涉及了3個棧,核心棧,TSS指定的棧(儲存使用者程序的context),使用者棧。中間那個棧隻是使用者态和核心态的過渡。

繼續閱讀