I/O Device

05 May 2020

Intro

  • System bus: 讓device跟CPU溝通的線路, 多個device會用同條system bus (最常見的就是PCI bus)
  • Device port: 基本上是device跟CPU溝通的接口, device port上面有四種register負責個別的工作:
    • Status: 這個port告訴紀錄現在device的狀態(ready,busy,error)
    • Control: command給device(read/write…)
    • Data-in: 送資料到CPU(1 word, 1byte大小)
    • Data-out: 收CPU資料的(1word, 1byte)
  • Device Controller: hardware component提供device跟system bus連接的interface(例如就是device跟PCI bus溝通的介面跟方法, 如下圖的SCSI controller, disk controller等等).
  • Device driver:基本上就是作業系統端讓作業系統跟硬體device controller互動的接口, 因為OS是直接連接管裡硬體資源, 所以管理的辦法就是透過device driver來發送請求給硬體, device driver通常被歸類在OS kernel內(micro kernel除外), 所以是在privilege mode運行

usb比較特別是expansion bus(因為是可以attach,detach), 所以是先connect到expansion bus(usb bus), 而不是直接connect到PCI bus, 主機板內建會有usb controller來連接usb bus跟PCI bus.

所以用這樣的basic approach, 就要一值發read command給device然後data-in一次又只能傳很小的資料, 這樣在像disk operation時要讀MB,GB的資料會很久

Hierachy

OS kernel會有一個IO subsystem來跟各個kernel理的device driver互動, device driver就是一個底層software跟device controller互動, device driver要能夠知道device controller有哪些指令來做(每個controller的protocol, 指令集不同), 所以可能兩個keyboard但他們的controller不同, 就需要兩個device driver.

Lifecycle of a read function call

檔案的存放是放在硬碟裡面, 但具體怎麼放跟OS怎麼存取是透過檔案系統這個程式來負責(例如Windows FAT, linux ext3,4), 當存取這些檔案時, filesystem會知道該檔案會分散在硬碟中的那些地方去把他讀出來(因為硬碟也是會分多個sector, 每個sector大小固定, 所以大檔案會被拆成存在不同的sector這樣)

當我們call fopen的c runtime library時, 該library的時做細節就是會呼叫system call open/read來讀資料, OS把read請求送到檔案系統, 檔案系統收到後去找檔案存在的對應的磁碟空間, 然後檔案系統像hard disk的device driver發送讀資料請求, device driver就會去跟hard disk controller發出讀取的命令, 這部分命令可以是MMIO,PIO來讀寫port的形式來發出命令

CPU北橋南橋

早期CPU核心頻率不高,記憶體的bandwidth也不會大到誇張, 所以可以用上圖的同一個Bus的方式來傳遞資料就好.

但後來CPU頻率遠高於IO device而且記憶體,GPU等等需要大量交換傳送資料, 所以CPU為了應付這樣快慢差距很大的device資料傳輸, 因此把CPU分成南北橋. 北橋負責接收記憶體,顯卡等等傳輸快速的設備的資料, 南橋(ISA bridge)則負責低速的設備(鍵盤滑鼠,usb等等), 蒐集到資料後匯集再送到北橋(PCI bridge), 後來速度需求更快, 所以又有了PCIe等等的新的硬體設備出現

而記憶體則有自己的memory bus跟北橋連接

port address

因為system bus是大家共用傳資料, 基本上有點像broadcast方式, 要知道要把資料傳到哪個device就必須要用address的方式傳給特定device, 所以machine會給IO device port一個preset的address, 所以資料送到system bus, device會檢查他是否是送到自己的port address的來判斷這個資料是不是要接收. 但這是low level的部份, 由OS的IO subsystem來handle, 使用者不會注意到這段 因為被abstract起來了.

Device 分類

然後從high-level觀點(programmer, 一般使用者觀點), IO device可以以這幾類區分, 功能不同:

  • character device(keyboard)跟block device(disk,基本上就是指這個device一次讀寫的資料量), 這資料會在ls -l /dev時會在第一個bit specify
  • sequential or random access: 一樣, keyboard就是輸入一個字元就是sequential access,
  • Synchronous/Async: 大部分device是async, IO system call是 sync(OS blocking IO)
  • sharable or dedicated: 只能一個process access或可以多個acess
  • Speed: graph card快, keyboard慢

OS跟IO device 機制

  • File system for IO device

Linux透過high-level file system的方式來跟各個device互動, linux會把各種東西以file的方式呈現, 跟device互動的interface也不例外, 所以/dev下面的檔案, 每個檔案就是一個IO device的接口, 可以透過讀寫, 設定各個檔案來跟IO device互動. OS會接收使用者對這些檔案的修改然後去做對應的事情到IO device上.

  • Access Control
  • Device driver來運作device
  • Device allocation(machine bring up時(?)
  • IO scheduling
  • Error handling

CPU IO device溝通

Reference:
blog csdn
tencent Device IO名詞解釋

CPU會先busy wait controller的status register等到便idle,接著透過設定各種Controller的register(command register, data-out register)來告知Controller要做的事,controller也會設定status register告知CPU他現在是忙還是閒, 這種缺點就是要CPU不斷的去check或wait controller直到他把status register設成idle才能真的要求controller去執行command, 然後IO device速度很慢. 然後data-out, data-in的register很小, 一次只能傳很少資料

Port-mapped IO(PIO/PMIO)

就是CPU透過特定指令(X86:IN,OUT)來直接跟device port溝通傳資料, 這種方式就沒有所謂的memory mapping的方式, CPU跟device沒有shared memory, Device自己有自己的address space(就是port的address), 而CPU是透過IN,OUT等指令來傳遞資料到這些port address, 這缺點就是Device IO都需要透過CPU來達成, 而MMIO則是可以讓device自己去讀memory不用CPU的幫忙.

Memory-mapped IO(MMIO)

透過memory mapping的方式讓CPU跟Device IO溝通, 把device IO內部的memory buffer或者register map到virtual memory上, 這樣就可以用正常的那些memory access instruction來跟device溝通. 但整個過程(不論是資料送到device, 或device送到memory) 都需要CPU的幫忙, 因為資料送到device就是透過正常的memory access instruction. 因此跟DMA的差別在於 他沒有一個特殊的DMA controller來進行memory資料讀取, memory資料讀取仍要透過CPU, 只是page table mapping時有些page是map到device的memory buffer上

Direct Memory Access (DMA)

解決CPU IO device溝通中,每次傳送資料太少的問題, 與其每次都要發command, 修改controller register來讀寫資料, 不如就建立一塊CPU跟device間的shared memory, 兩者都可以透過DMA Controller這個hardware來access這塊記憶體和傳輸資料, 這樣就可以一次讀寫大量資料, 而且可以async access(CPU把資料寫進去Memory就可以去做自己的事, 資料傳給device之類的透過DMA controller來做, Device idle時再去取資料就好). DMA Controller讀寫完資料, DMA controller會再跟CPU說資料已經讀寫完畢. 基本上概念就是CPU issue一個request給DMA controller, 內部包含要從哪個source address讀多少資料到destination address. 小問題是DMA Controller做memory access, CPU也做memory access, 而且兩者共用system bus, 有時會競爭memory access的bandwidth(system bus的傳輸)

Hardware Interrupt

解決上述CPU和等待hardware idle的問題 (busy waiting), CPU不用等IO device, IO device idle時或者事情做完時指需要發interrupt通知CPU即可.

因此變成CPU發起IO operation的request, 接著就去做自己的事, IO做處理, 不論是做完或者input/output ready時發起interrupt給CPU, CPU收到interrupt 去Interrupt descriptor table找對應的interrupt handler來處理這個device IO interrupt(context switch), 做完在resume回原本執行的program. Intel x86有自己的interrupt descriptor, 每個interrupt有對應的Number, 例如page fault是14, invalid opcode是6…

Hardware interrupt允許CPU跟device間async溝通, CPU不用busy wait, 只有等收到interrupt時再透過指定的interrupt handler來做事即可.

Interrupt & Exception

Exception通常是指在program execution時發生的錯誤(如divide by 0, seg fault之類的), 他通常是program level的synchronous的事情, 也就是說這種exception是在CPU跑到某行指令發生的(不是asynchronous), 然後他影響範圍是這個program跳到他的exception handler的部份, 機制差不多, 但他是作用在那個program level的, 不會有context switch的部份, 只會PC移到指定地方.

而interrupt通常是CPU在做某件事, 突然收到一個訊息要去做另一件事(async), 然後會是OS-level的IDT look up在交由特定的interrupt handler來處理, 會involve context switching

IO Buffer

device之間的速度差很多(例如要把從網路上下載的資料存到disk, 網路下載速度很慢但disk相對快很多,更別提一個大檔案網路傳輸會因為MTU被切成多個小封包), 每次收到資料在寫進disk overhead太大(device r/w 需要switch, 然後每次只寫一點點, context switch就很多), 所以先把資料存在OS(buffering), 一定資料量在一起寫入disk

OS-side Buffer

OS端也會對於Device IO的資料需要有buffer來存, 因為以下原因: 資料傳輸速度的mismatch(例如要從網路讀資料把它存入disk, 但網路速度遠比write disk慢很多, 所以要有buffer存network data, 一段時間後再write disk, 減少過多的write disk operation), 或者不同device傳資料的方式大小不同, 但最終要在OS端assemble, 另外就是減少不必要的disk write, 增加OS的performance(cache住資料,達到一定量資料在write back到disk)