一覧OS-03-1 · 入出力と記憶装置

デバイス制御と割り込み・DMA

状態確認、データ移動、完了通知を分け、CPU転送とDMAの処理を追います。

説明

OSのデバイスドライバは、装置の制御レジスタへ要求を書き、状態や完了を確認します。MMIOはレジスタをメモリアドレス空間へ割り当てる方式です。ポーリングと割込みは状態を知る方法、CPU転送とDMAはデータを移す方法であり、別の観点です。

8バイトを装置からメモリへ転送するモデルを使います。レジスタのビットと確認時刻を調べ、CPUが各バイトを移す場合からDMAのアドレス・長さ指定へ進みます。最後に完了通知とバッファの利用時点を対応付けます。

1制御レジスタの配置

例の装置は0x4000をSTATUS、0x4004をCOMMAND、0x4008をDATAとします。STATUSのビット0がBUSY、ビット1がDONEです。

register_map = {0x4000:'STATUS',0x4004:'COMMAND',0x4008:'DATA'}装置レジスタのアドレス; BUSY,DONE = 1,2状態ビット; status = 0待機状態; [(hex(address),name) for address,name in register_map.items()],bool(status&BUSY),bool(status&DONE)配置と状態

初期状態ではBUSYもDONEも0です。MMIOの読書きには装置固有の意味や副作用があり、一般のRAMと同じように自由に再読出し・並べ替えしてよいとは限りません。

2要求から完了までの状態

COMMANDへREAD=1を設定するとBUSYになり、時刻5でDONEへ変わるモデルにします。データはASCIIのABCDEFGHです。

command = 1読出し要求; payload = b'ABCDEFGH'転送する8バイト; states = {tick:BUSY if tick < 5 else DONE for tick in range(7)}時刻ごとの状態; [(tick,f'{value:02b}') for tick,value in states.items()],payload.hex(' ')状態ビットとデータ

状態は01から10へ変わります。要求を設定した時点とデータが準備できた時点は異なります。データ内容は41〜48の8バイトです。

3確認間隔で検知時刻が変わる

毎刻み読む場合と、2刻みごとに読む場合を比較します。最初にDONEを観測した時刻で確認を止めます。

fast_checks = list(range(6)); slow_checks = list(range(0,7,2))二つのポーリング間隔; [(checks,next(t for t in checks if states[t]&DONE)) for checks in [fast_checks,slow_checks]]確認時刻と最初のDONE

毎刻みでは6回読んで時刻5、2刻みごとでは4回読んで時刻6に完了を知ります。確認回数を減らすと、完了から検知までの遅れが増える場合があります。

4CPUがデータを移す場合

データ準備後に、CPUが装置のDATAから1バイトずつ読み、メモリへ書く方式を考えます。元とコピーの各位置を照合します。

cpu_buffer = bytearray(len(payload))転送先バッファ; cpu_buffer[:] = bytes(byte for byte in payload)各バイトをコピー; list(enumerate(cpu_buffer)),bytes(cpu_buffer) == payload,len(payload)*2位置・一致・データアクセス数

8回の装置読出しと8回のメモリ書込みで、モデル上は16回のデータアクセスです。割込みで準備完了を知った場合でも、データをCPUが運ぶならこの転送処理は残ります。

5DMAへ渡す記述子

説明用の記述子を64ビットDMAアドレス・32ビット長さ・32ビットフラグで構成します。全てリトルエンディアン、フラグ1を装置からメモリへの方向と定義します。

import structバイナリ構造の変換; dma_address,length,flags = 0x3000,len(payload),1アドレス・長さ・方向; descriptor = struct.pack('<QII',dma_address,length,flags)16バイトへ符号化; descriptor.hex(' '),struct.unpack('<QII',descriptor)バイト列と復号

16バイトの記述子に転送先0x3000、長さ8、方向1が入ります。これは教材で定めた記述子形式で、各装置の実際の形式は仕様に従います。CPUは設定を行い、データ本体の移動をDMA機構へ任せます。

6CPUと装置でアドレスが異なる

CPU仮想アドレス0x8000とDMAアドレス0x3000が、同じ物理バッファ0x10000へ対応するとします。対応範囲は16バイトです。

cpu_virtual,physical,dma_base,capacity = 0x8000,0x10000,0x3000,16二つの入口と対応範囲; translated = physical+(dma_address-dma_base)DMAから物理位置へ変換; valid = dma_base <= dma_address and dma_address+length <= dma_base+capacity転送全体が範囲内か; hex(cpu_virtual),hex(dma_address),hex(translated),validアドレスと範囲判定

二つのアドレスは同じ数とは限りません。IOMMUなどが装置用アドレスを物理メモリへ変換する場合があります。ドライバはDMA APIで得た装置向けアドレスと、転送方向・範囲を適切に管理します。

7転送完了と割込み通知

DMAが8バイトをバッファへ転送し、DONEと割込み要求を立てる順序を表します。

dma_buffer = bytearray(capacity)16バイトの領域; dma_buffer[:length] = payloadDMAによる転送を反映; status = DONE; interrupt_pending = True完了と通知; dma_buffer.hex(' '),f'{status:02b}',interrupt_pending転送領域と状態

先頭8バイトがデータ、残り8バイトは初期0です。完了通知の前に転送が完了し、CPUが利用できる状態になるよう、実際のドライバではDMAの同期規則や必要なメモリ順序を守ります。

8ドライバが完了を処理する

割込み処理で状態を確認し、完了した8バイトを受け取った後に通知を確認済みにします。ここではDONEと通知を0へ戻す規則です。

received = bytes(dma_buffer[:length]) if status&DONE else None完了状態でデータを受け取る; status = 0; interrupt_pending = False通知を確認済みにする; received,status,interrupt_pending受信内容と待機状態

受信結果はABCDEFGHです。割込みが来たという事実だけでなく、どの要求が完了したかと状態を確認します。DMAを使っても、バッファの準備、要求、完了処理というCPUとドライバの仕事は残ります。

まとめ

ポーリング・割込みは通知の方法、CPU転送・DMAはデータ移動の方法です。

MMIOレジスタとDMA記述子は装置の規則に従い、アドレスと長さを管理します。

CPUアドレスとDMAアドレスを区別し、完了と同期を確認してバッファを利用します。

操作履歴と解説を振り返れます。

Python 3

1
register_map = {0x4000:'STATUS',0x4004:'COMMAND',0x4008:'DATA'}; BUSY,DONE = 1,2; status = 0; [(hex(address),name) for address,name in register_map.items()],bool(status&BUSY),bool(status&DONE)
([('0x4000', 'STATUS'), ('0x4004', 'COMMAND'), ('0x4008', 'DATA')], False, False)
2
command = 1; payload = b'ABCDEFGH'; states = {tick:BUSY if tick < 5 else DONE for tick in range(7)}; [(tick,f'{value:02b}') for tick,value in states.items()],payload.hex(' ')
([(0, '01'), (1, '01'), (2, '01'), (3, '01'), (4, '01'), (5, '10'), (6, '10')], '41 42 43 44 45 46 47 48')
3
fast_checks = list(range(6)); slow_checks = list(range(0,7,2)); [(checks,next(t for t in checks if states[t]&DONE)) for checks in [fast_checks,slow_checks]]
[([0, 1, 2, 3, 4, 5], 5), ([0, 2, 4, 6], 6)]
4
cpu_buffer = bytearray(len(payload)); cpu_buffer[:] = bytes(byte for byte in payload); list(enumerate(cpu_buffer)),bytes(cpu_buffer) == payload,len(payload)*2
([(0, 65), (1, 66), (2, 67), (3, 68), (4, 69), (5, 70), (6, 71), (7, 72)], True, 16)
5
import struct; dma_address,length,flags = 0x3000,len(payload),1; descriptor = struct.pack('<QII',dma_address,length,flags); descriptor.hex(' '),struct.unpack('<QII',descriptor)
('00 30 00 00 00 00 00 00 08 00 00 00 01 00 00 00', (12288, 8, 1))
6
cpu_virtual,physical,dma_base,capacity = 0x8000,0x10000,0x3000,16; translated = physical+(dma_address-dma_base); valid = dma_base <= dma_address and dma_address+length <= dma_base+capacity; hex(cpu_virtual),hex(dma_address),hex(translated),valid
('0x8000', '0x3000', '0x10000', True)
7
dma_buffer = bytearray(capacity); dma_buffer[:length] = payload; status = DONE; interrupt_pending = True; dma_buffer.hex(' '),f'{status:02b}',interrupt_pending
('41 42 43 44 45 46 47 48 00 00 00 00 00 00 00 00', '10', True)
8
received = bytes(dma_buffer[:length]) if status&DONE else None; status = 0; interrupt_pending = False; received,status,interrupt_pending
(b'ABCDEFGH', 0, False)

演習完了