(2021) 23 [持久化] I/O設備與驅動
南京大學操作系統課蔣炎巖老師網絡課程筆記。
視頻:https://www.bilibili.com/video/BV1HN41197Ko?p=23
講義:http://jyywiki.cn/OS/2021/slides/13.slides#/
背景
很多人 (你們的同學們、家長們) 都有一個認識,“計算機系就是裝 (修) 電腦的”,因為大家對電腦的印象只有 I/O 設備。但上了計算機系發現根本不是這么回事啊,根本沒有直接教怎么 “修電腦”。
但不應該是這樣的!
- 學 “計算機” 的人不僅會修電腦,還會造電腦!
只要我們有一個設備的手冊或者文檔等充足的信息,就可以結合自己在操作系統等課程上的知識,來查找并修復問題。
本次課的內容和目標
理解 “I/O 設備是什么”
- 鍵盤
- 磁盤
- 中斷控制器
- 總線
- DMA
- GPU
學習 I/O 設備在操作系統中的抽象
- 設備 = 可以讀、寫、控制的對象
- “設備驅動程序”
常見 I/O 設備
孤獨的CPU
CPU 只是 “無情的指令執行機器”,不斷地從內存上進行取指令、譯碼、執行。
我們是通過各種各樣IO設備實現與CPU的交互的。
CPU眼中的IO設備
I/O 設備是一個能與 CPU 交換數據的接口
通俗來講:
- 就是 “幾組線”
- 每一組線有約定的功能 (RTFM)
- CPU 通過握手信號從線上讀出/寫入數據
- 每一組線有自己的地址
- CPU 可以直接使用指令 (in/out/MMIO) 和設備交換數據
在CPU看來,與常見的IO設備的交互,就是按照特定的規則(RTFM)來讀寫一組寄存器。比如下面介紹的鍵盤控制器和磁盤控制器。
鍵盤控制器
IBM PC/AT 8042 PS/2 (Keyboard) Controller
- “硬編碼” 到兩個 I/O port:
0x60
(data),0x64
(status/command)
Command Byte | Use | 說明 |
---|---|---|
0xED | LED 燈控 | ScrollLock/NumLock/CapsLock |
0xF3 | 設置重復速度 | 30Hz - 2Hz; Delay: 250 - 1000ms |
0xF4/0xF5 | 打開/關閉 | N/A |
0xFE | 重新發送 | N/A |
0xFF | RESET | N/A |
參考 AbstractMachine 的鍵盤部分實現
磁盤控制器
ATA (Advanced Technology Attachment)
- IDE (Integrated Drive Electronics) 接口磁盤
- primary:
0x1f0 - 0x1f7
; secondary:0x170 - 0x177
- primary:
void readsect(void *dst, int sect) {waitdisk();out_byte(0x1f2, 1); // sector count (1)out_byte(0x1f3, sect); // sectorout_byte(0x1f4, sect >> 8); // cylinder (low)out_byte(0x1f5, sect >> 16); // cylinder (high)out_byte(0x1f6, (sect >> 24) | 0xe0); // driveout_byte(0x1f7, 0x20); // command (write)waitdisk();for (int i = 0; i < SECTSIZE / 4; i ++)((uint32_t *)dst)[i] = in_long(0x1f0); // data
}
總之,以上這些IO設備,在CPU看來,就是按照特定的規則(RTFM)來讀寫一組寄存器。
特殊的IO設備
中斷控制器
我們的設備中通常有許多IO設備,那我們只想在這些IO設備有有意義的數據/信號時,CPU才回去處理這些數據,而在平時IO設備沒有數據時,不要打擾CPU的其他正常工作。這就要通過中斷來實現。
CPU有一個中斷引腳收到某個特定的電信號會觸發中斷
- 保存 5 個寄存器 (cs, rip, rflags, ss, rsp)
- 跳轉到中斷向量表對應項執行
CPU有且只有一個中斷引腳(如圖中6502的4號引腳 IRQˉ\bar{IRQ}IRQˉ?)。那CPU是怎樣管理各種不同的中斷,以及不同中斷到來時的處理優先級的呢?這就要用到一個特殊的IO設備:中斷控制器。
系統中的其他設備可以向中斷控制器連線。從而實現上面提到的中斷優先級的判斷、中斷屏蔽等功能。
- Intel 8259 PIC(programmable interrupt controller),(微機原理中也介紹過,配合8086),可以設置中斷屏蔽、中斷觸發等……
- APIC (Advanced PIC)(現代CPU中使用的中斷控制器)
- local APIC(每個CPU內部的中斷管理): 中斷向量表, IPI, 時鐘, ……
- I/O APIC(外部IO的中斷管理): 其他 I/O 設備
總線
想法
如果你只造 “一臺計算機”,隨便給每個設備定一個端口/地址,用 mux 連接到 CPU 就行。
但如果你希望給未來留點空間?
- 想賣大價錢的 “大型機”
- IBM, DEC, …
- 車庫里造出來的 “微型機”
- 名垂青史的夢想家
- 都希望接入更多 I/O 設備
- 甚至是未知的設備,即可擴展性
在多個IO設備時,每個IO設備中的每個寄存器都有一個自己的地址,那肯定不能將這些寄存器全部直連到CPU上。
總線介紹
總線提供地址到設備的轉發。把從CPU傳來的地址(總線地址)和數據轉發到相應的設備上(按照上面說的每個設備中的每個寄存器的地址)。例如: port I/O 的端口就是總線上的地址,IBM PC 的 CPU 其實只看到這一個 I/O 設備。
這樣 CPU 只需要直連一個總線 (例如今天的 PCI總線 (Peripheral Component Interconnect) 就行了
- 總線可以負責IO設備寄存器的地址編址
- 總線可以橋接其他總線 (例如 PCI → USB)
lspci -tv
和lsusb -tv
: 查看系統中總線上的設備- 概念簡單,實際非常復雜
- 電氣特性、burst 傳輸、中斷……
例子:PCI Device Probe
DMA(Direct Memory Access)
想法
假設程序希望寫入 1 GB 的數據到磁盤
- 即便磁盤已經準備好,依然需要非常浪費緩慢的循環
- out 指令寫入的是設備緩沖區,需要去總線上繞一圈
- cache disable; store 其實很慢的
for (int i = 0; i < 1 GB / 4; i++) {outl(PORT, ((u32 *)buf)[i]);
}
能否把 CPU 從執行循環中解放出來?
- 比如,在系統里加一個 CPU,專門復制數據?
- 好像
memcpy_to_port(ATA0, buf, length);
DMA介紹
-
DMA可以理解為一個只能執行memcpy這一條指令的迷你處理器,它只負責直接將內存上的內容搬運的磁盤。而不需要真正的CPU的參與,不需要占用真正的CPU時間。實際實現:直接把 DMA 控制器連接在總線和內存。
-
由于DMA處理器只進行memcpy這樣一條指令,因此它的實現比通用的真正的CPU簡單得多。
-
DMA不僅可以完成內存和設備之間的內容搬運,而也可以是內存和內存之間。即可以有:
- memory → memory
- memory → device (register)
- device (register) → memory
-
PCI 總線支持 DMA,即PCI中自帶DMA,這就是為什么 CPU 會有 PCIe lanes。
GPU
在CPU眼中,顯卡(GPU)也是一種IO設備,通過讀寫某些寄存器來交互。
一切皆可計算
for (int i = 1; i <= H; i++) {for (int j = 1; j <= W; j++)putchar(j <= i ? '*' : ' ');putchar('\n');
}
難辦的是性能
- NES: 6502 @ 1.79Mhz; IPC = 0.43
- 屏幕共有 256 x 240 = 61K 像素 (256 色)
- 60FPS → 每一幀必須在 ~10K 條指令內完成
- 如何在有限的 CPU 運算力下實現 60Hz?
既然能做一個只專注于 memcpy 的硬件DMA,為什么不能做一個畫圖的硬件?在輸出圖形時,可以認為對每個像素點做的是相同的計算指令,不同的數據的計算,因此,可以認為是一種并行計算。因此,我們也可以用一個專注于處理并行計算的非通用功能的處理器:GPU,只用來處理圖形相關的這類并行計算。
現代GPU:一個通用計算設備
一個完整的眾核多處理器系統
- 注重大量并行相似的任務
- 程序使用例如 OpenGL, CUDA, OpenCL, … 書寫
- 程序保存在內存 (顯存) 中
- nvcc: 把 main 編譯/鏈接成 ELF; kernel 編譯成 GPU 指令
- 數據也保存在內存 (顯存) 中
- 可以輸出到視頻接口 (DP, HDMI, …)
- 也可以通過 DMA 傳回系統內存
gpgpu:通用圖形處理器
通過對處理圖形顯示這類并行計算任務的推廣,出現了通用圖形處理器。它們不再只關注圖形處理,而是可以針對更廣范圍的并行計算,比如神經網絡的計算、矩陣計算。
通用圖形處理器(General-purpose computing on graphics processing units,簡稱GPGPU),是一種利用處理圖形任務的圖形處理器來計算原本由中央處理器處理的通用計算任務。這些通用計算常常與圖形處理沒有任何關系。由于現代圖形處理器強大的并行處理能力和可編程流水線,令流處理器可以處理非圖形數據。特別在面對單指令流多數據流(SIMD),且數據處理的運算量遠大于數據調度和傳輸的需要時,通用圖形處理器在性能上大大超越了傳統的中央處理器應用程序。
I/O設備的抽象
I/O設備是操作系統中的對象(文件)
無論何種 I/O 設備,都是可以讀 (read) 寫 (write) 的字節序列 (流或數組)。
I/O設備應該是操作系統中怎樣的對象?操作系統最簡單,或者說最不負責任的做法,當然是可以直接將IO設備的寄存器,或者PCI總線的控制寄存器,暴露給應用程序。微內核的操作系統就是這么做的,微內核的系統只做最少、最必要的部分。
但如果操作系統再負責一點,將我們的每個IO設備(比如鍵盤、磁盤等)都抽象成操作系統中的一個對象。
操作系統:設備 = 支持以下三種操作的對象 (文件)
- read: 從設備某個指定的位置讀出數據
- write: 向設備某個指定位置寫入數據
- ioctl: 讀取/設置設備的狀態
而以上這三種操作,恰恰就是文件系統中,一個文件應該支持的操作。 Everything is a file !
設備驅動程序:實現抽象
設備驅動程序把對設備的 read/write/ioctl 系統調用 “翻譯” 成設備的寄存器命令序列。
以 “面向對象” 的方式訪問 I/O 設備,設備 = 支持 read, write, ioctl, … 功能的對象。
例子:/dev 中的對象
/dev/pts/[x]
- pseudo terminal/dev/zero
- “零” 設備/dev/null
- “null” 設備,一般不想要的輸出可以直接重定位到這個設備。/dev/random
,/dev/urandom
- 隨機數生成器
未必一定要有物理設備
設備驅動程序:將設備抽象為一個對象和操作,未必一定要有物理設備,比如/dev/null
, /dev/urandom
。就是操作系統中不需要物理設備的一種設備/文件/對象的抽象。
試一試
-
試一試執行命令:
head -c 512 [device] | xxd
,并觀察它們的strace
來查看訪問設備的系統調用。 -
tty
查看當前終端,輸出/dev/pts/3
,即當前終端是3號終端。如果我們再打開一個終端并向之前的3號終端輸出,是可以直接出現在3號終端上的:
echo Hello > /dev/pts/3
。也就是說,每個打開的終端也被看做是在設備目錄
/dev
下的一個設備。
存儲設備的抽象
磁盤 (存儲設備) 的訪問特性與其他的設備的讀寫略有不同,是以塊為單位的。
- 以數據塊 (block) 為單位訪問,傳輸有 “最小單元”,塊內不支持任意隨機訪問
- 大吞吐量,使用 DMA 傳送數據。
- 應用程序不直接訪問
- 訪問者通常是文件系統 (維護磁盤上的數據結構)
- 大量并發的訪問 (操作系統中的進程都要訪問文件系統)
對比一下終端和 GPU,的確是很不一樣的設備
- 終端:小數據量、直接流式傳輸
- GPU:大數據量、DMA 傳輸
總結
本次課內容與目標
- 理解 “什么是 I/O 設備”
- 終端、鍵盤、鼠標、總線、DMA、GPU……
- 理解 “I/O 設備在操作系統中的抽象”
- 可以讀/寫/控制的對象
Takeaway messages
- 如果你 “自己造一臺計算機”,你會發現這一切都是自然的
- “不容易理解” 的部分是隨時間積累的復雜性