I/O命令

すでに述べられているように、インテル86系のCPUを使用したI/Oを行なう 場合、I/O空間とメモリ空間は別々の命令によってアクセスされます。 I/OデバイスのI/O空間へのアクセスはアセンブラ命令のI/O命令を利用する のに対して、I/Oデバイスのメモリ空間へのアクセス方法は、 CPUがプログラムやデータを格納するために利用する通常のメモリへのアクセス方法 と同じように、ポインタを使って行なわれます。

Linuxで定義されるI/O空間へのアクセス関数

I/O命令の代表的なものをあげて見ます。 これらの関数は/usr/include/sys/io.hで下記のように定義されています。
extern inline unsigned char
inb (unsigned short port)
{
  unsigned char _v;

  __asm__ __volatile__ ("inb %w1,%0":"=a" (_v):"Nd" (port));
  return _v;
}
extern inline unsigned int
inl (unsigned short port)
{
  unsigned int _v;

  __asm__ __volatile__ ("inl %w1,%0":"=a" (_v):"Nd" (port));
  return _v;

extern inline void
outb (unsigned char value, unsigned short port)
{
  __asm__ __volatile__ ("outb %b0,%w1": :"a" (value), "Nd" (port));
}

}
extern inline void
outl (unsigned int value, unsigned short port)
{
  __asm__ __volatile__ ("outl %0,%w1": :"a" (value), "Nd" (port));
}
上記の定義から、I/O空間へのアクセスはアセンブラ命令へと変換される ことがおわかりでしょう。

Linuxで定義されるメモリ空間へのアクセス関数

I/O命令の代表的なものをあげて見ます。 これらの関数は/usr/include/asm/io.hで下記のように定義されています。
#define readb(addr) (*(volatile unsigned char *) __io_virt(addr))
#define readw(addr) (*(volatile unsigned short *) __io_virt(addr))
#define readl(addr) (*(volatile unsigned int *) __io_virt(addr))
#define writeb(b,addr) (*(volatile unsigned char *) __io_virt(addr) = (b))
#define writew(b,addr) (*(volatile unsigned short *) __io_virt(addr) = (b))
#define writel(b,addr) (*(volatile unsigned int *) __io_virt(addr) = (b))
#define __io_virt(x) ((void *)(x))
御覧の通り、メモリ空間へはポインタを利用したシンプルなアクセスになっています。

I/O空間へのアクセスのためのアセンブラ

次のコードはC言語で書かれたI/Oアクセスの例題です。
#include 
main() {
char a;

a = inb(0x70);
outb(0x70,a);

}
これをコンパイルします。
 gcc -O -S t.c
ここで、-Oは必要な、オプティマイズのフラグです。-Sはアセンブラコードを 出力するためのフラグです。
        .file   "t.c"
        .version        "01.01"
gcc2_compiled.:
.text
        .align 4
.globl main
        .type    main,@function
main:
        pushl %ebp
        movl %esp,%ebp
#APP
        inb $112,%al
#NO_APP
        movb %al,%dl
        movsbw %dl,%dx
        movb $112,%al
#APP
        outb %al,%dx
#NO_APP
        leave
        ret
.Lfe1:
        .size    main,.Lfe1-main
        .ident  "GCC: (GNU) 2.7.2.3"
となり、inb()関数が、下記のようにinbアセンブラ命令に変換されます。
inb $112,%al
また、outb()関数が、下記のようにinbアセンブラ命令に変換されます。
 outb %al,%dx

ユーザプログラムからのI/O空間へのアクセス

ユーザプログラムからI/O空間へアクセスする最も簡単は方法を示します。 これらの方法は実行時においてrootの特権が必要とされますが、 大変便利です。デバイスドライバを開発する時、とりあえずこれらの 方法でコードのデバッグをしておいて、デバイスへのアクセス方法が 確立してコードが安定したら、デバイスドライバに組み込むということが 可能です。ただし、これらの方法はユーザプログラムから簡単にシステムを 破壊することが可能になります。注意して使いましょう。 I/Oアドレスが0から0x3FFまででしたらiopermシステムコールを使うのが いいでしょう。次のように使います。
#include <sys/io.h>
#define ADDR 0x310
#define SIZE 10

    if (ioperm(ADDR, SIZE, 1)) {
        printf("Can't get I/O permissions \n");
        exit (-1); 
      }
これはI/Oアドレス0x310から10バイト分アクセスを許可するものです。 I/Oアドレスが0x400以上の場合iopermは使えません。 そこで登場するのが
#include <sys/io.h>

    if( iopl( 3 ) ) {
        printf("can not change the privilege level\n");
        exit(0);
        }
このようにすれば65536ポート(0xFFFF)まで利用範囲が広がります。 さて、そのようにしてI/O空間の一部がユーザプログラムから利用可能に なるとI/O命令がそのまま使用できるようになります。 下記のコードはその一例です。
    int data;

    outl(RST, ADDR);
    data = inl( ADDR );