ハードウェアを意識したプログラミングの基礎(前編)
● ポート・マップトI/O
ポート・マップトI/Oのアドレス空間は,メモリとは完全に異なるため,メモリ・アクセスと同じ方法ではアクセスできません.そのため,CPUはI/Oポート・アドレス空間にアクセスするための専用のアセンブリ命令を持っています.inやoutという命令です.
in src, dest
out src, dest
Linuxはもともとx86用に作られたカーネルです.そのため,x86由来のコードが今でも多く残っています.in()/out()マクロもその一つです.Linuxでは,x86のI/Oアクセス用命令をC言語で使えるようにしたマクロです.アクセス・サイズによってinb,inw,inlという名前で定義されています.リスト4は8ビット・アクセス用のマクロinb()です.
static inline unsigned char inb(int port) {
unsigned char value;
__asm__ __volatile__("inb %w1, %b0" : "=a"(value) : "Nd"(port));
return value;
}
リスト4 マクロinb(include/asm-x86/io_32.h)
● メモリ・マップトI/O
メモリ・マップトI/Oの場合,デバイス(CPUの周辺機能)はメモリと同じ空間にあるように見えます.このためデバイスへのアクセスに,メモリへのアクセスと同じ命令を使用します.
当たり前ですが,x86以外のアーキテクチャではinやoutの命令はありません.そのため,inbやoutbのマクロをCPUに合った形で提供する必要があります.メモリ・マップトI/Oのアーキテクチャは,x86のマクロと同じ名前のマクロをメモリ・アクセス用に提供しています.
● アクセス方法の抽象化
Linuxでは,ポート・マップトI/O用にinX()やoutX()注7のマクロを,メモリ・マップトI/O用にreadX()やwriteX()のマクロを用意しています.しかしこれではまだ,アクセス方法に合わせてコードを書かなければなりません.
注7;Xにはb:8ビット,w:16ビット,l:32ビットが入る.
そこでLinuxでは,アクセス方法に合わせて適切なマクロに置き換えてくれるマクロが用意されています.ioreadX()/iowriteX()マクロです.このマクロは,与えられたアドレスがI/O空間なのかメモリ空間なのかを判定してくれます(図7).このマクロもinclude/asm/io.hで定義されています.
図7 ioread8(addr)の動作
● 直接書かれているアドレス
x86だけで使われていたデバイス,特にISAバスにつながるデバイス用のドライバは,デバイス・ドライバのコードの中にアドレスがそのまま埋め込まれていることもありました.これでは,たとえioread()/iowrite()でアクセス方法を抽象化できてもほかのアーキテクチャでは使いものになりません.
また,同じくメモリ・マップトI/Oを採用しているシステム注8でも,デバイスに割り当てるアドレスが異なることもあります.もし直接書く方法しかなければ,対応しているアーキテクチャの数だけアドレスを書き,#ifdefでコードを条件分岐させなければならなくなってしまいます.これではコードが読みづらくてたまりません.Linuxでは,これを解決するために「The Linux Driver Model」という考え方が導入されています.この話はLinux固有の考え方なので,本稿の最後で解説します.
注8;この場合CPUやCPUを含んだSoC,さらに同じCPUを使っていてもボード設計が異なるものも含む.