Linux I/O ポートプログラミング ミニハウツー 著者 : rjs@spider.compart.fi (Riku Saikkonen) 最終改定 : Aug 26 1996 訳者 : 川島 浩 (kei@sm.sony.co.jp) 訳の吟味/校正 : 山崎 康弘さん (hiro@koneeko.linux.or.jp) このドキュメントの著作権は 1995-1996 Riku Saikkonsen に属します。 詳細に関しては通常の Linux の HOWTO COPYRIGHT をご覧下さい。 この ハウツーでは、インテルの x86 プロセッサ上で走るプログラム上の、 ハードウエア I/O ポートのプログラミングと、Linux のユーザモードで短い時間 待ちをおこなうプログラム、などについて述べます。 私は前にも同じような(とても小さな) IO-Port-mini-HOWTO を書きましたが、 この文書はその続編です。 もし、なにか間違いがあったり、付け加えることなどありましたらぜひ 私にメイルしてください。(rjs@spider.compart.fi)... 前のバージョン (Dec 26 1995) からの変更点は: * ゲームポートのプログラミングに関する情報 * C 以外の言語 * nanosleep() * ペンティアムプロセッサのタイミング * コーデイング例を追加 * たくさんの小さな修正 などです。 C プログラムから I/O ポートを使う ... 普通の方法 I/O ポートをアクセスするためのルーチンは/usr/include/asm/io.h (または カーネルのソースパッケージの中の linux/include/asm-i386/io.h)に定義されて います。必要なルーチンは、この中でインラインマクロとして定義されていますので、 #include <asm/io.h> とするだけで十分でしょう。 特別なライブラリなどは不要です。 これらのルーチンを使うソースでは、gcc (少なくとも 2.7.2 またはそれよりも 前のバージョン)を使うときに、次のような制限があります。 最適化をオンにする(-O1 またはそれ以上)か、 そうでなければ asm/io.h をインクルードする前に #define extern という空の宣言をおこなっておく必要があります。 デバッグのために、「gcc -g -O」を使うことができます(少なくとも最近の gccでは) が、最適化されたコードではデバッガが変な挙動を示すことも あります。このような場合には、I/O ポートアクセスを含んだコードを独立 したファイルにしておいて、そのファイルのコンパイルの時にだけ最適化を オンにするという方法を使うこともできます。 ポートをアクセスする前に、そのポートをアクセスする許可をプログラムに与え なければなりません。これには、ioperm(2) 関数(unistd.h で宣言され、カーネル の中で定義されている)を呼び出します。 この関数はプログラムの最初の方で呼び出す必要があります。(I/O ポートアクセスを する前に) この関数の書式は ioperm(from, num, turn_on) です。 from は アクセスを許すポートの最初のアドレス。 num は、from からいくつの連続アドレスのアクセスを許すか。 を指定します。 例えば、ioperm(0x300, 5, 1) は、0x300 から 0x304 (合計で5つのポート) に対する許可を指定し、最後のパラメータでは、そのポートに対するアクセス許可を 与えるのか禁止するのかを論理値(true : 1 = 許可を与える, false : 0 = 許可を 禁止する)で指定します。 連続していないポートの場合には、複数回 ioperm() を呼び出して、適切な許可を 指定します。 ioperm() の詳細な文法に関しては ioperm(2) のマニュアルページを参照して下さい。 ioperm() を呼び出すには、そのプログラムが root 特権で走っていることが 必要です。つまり、そのプログラムをルートユーザで実行するか、セット UID root (訳注: 実行ファイルのオーナーを root にしておき、chmod 4755 hogehoge という やつです。)にしておく必要があります。 必要なポートに対する許可を ioperm() で与えたあとに、root 特権を落すこと もできます。 プログラムの最後で、陽に ioperm(..., 0) を呼び出して許可を落す必要は ありません。これはプログラムの終了時に自動的に行なわれますから。 ioperm() で設定した I/O に対するアクセス許可は、fork() や、exec() を通じて 引き継がれます。 また、root 以外のユーザに setuid した後にも引き継がれます。 ioperm() では、0x000 から 0x3ff までのポートに対するアクセス許可しか 与えることはできません。これよりも上のポートに対しては、iopl(2) を 使う必要があります。(iopl(2) を使うと一度にすべてのポートに対する アクセス許可を与えることになります。) すべてのポートに対してアクセス許可を与えるには、レベルパラメータとして 「3」を使います。つまり、「iopl(3);」という関数呼び出しをします。 もちろん、iopl() を呼び出す時には root 特権が必要です。 さて、実際にポートをアクセスする番です。 ポートから入力する場合にはinb(port); を呼び出します。 これは入力ポートからとってきたバイトデータを返します。 ポートに出力するには、outb(value, port); を呼び出します。 (パラメータの順番に注意して下さい。)(訳注:アセンブラや、MS-DOS のコンパイラ ライブラリなどの場合とは逆です。) ポートアドレス x と x+1 からワードデータを入力する場合には inw(x); を呼び出します。(アセンブラのINW とまったく同じようにそれぞれのポートから 1バイトずつ読んできて、ワードデータを構成するわけですが) ふたつのポートにワード(2バイト)を出力するには outw(value, x); を使います。 inb_p(), outb_p(), inw_p(), outw_p() といったマクロは、上に述べたのと 同じ動作をしますが、ポートにアクセスした後に少しだけ(約1マイクロ秒) ウエイトします。 asm/io.h をインクルードする前に REALLY_SLOW_IO を #define すると、 このウエイト時間を4マイクロ秒に変えることができます。 これらのマクロは通常、0x80 番地の I/O ポートに出力することでウエイトを 実現しています。(#define SLOW_IO_BY_JUMPING している場合には別の方法が 使われますが、これはあまり正確な遅延をおこなうことができないはずです。) このような理由から、0x80 番地のポートに対する ioperm()を先に実行しておく必要が あります。(0x80 番地のポートに対する出力はシステムに対してなんら影響を 与えることはないはずです。) さらにさまざまな遅延を行なう方法については、以下を読み進めて下さい。 そこそこ最近のリリースの man ページには、これらのマクロに関する説明があります。 トラブルシューティング: Q1. ポートをアクセスした時に segmentation faults が起きてしまいます。 A1. プログラムが root 特権を持っていないか、なんらかの理由で ioperm() を呼び出した時に失敗しているのでしょう。ioperm() の返り値を調べてみて ください。 Q2. in*() とか out*() とかの関数がどこにもみつかりません。gcc が undefined reference とか文句を言ってきます。 A2. コンパイルの時に最適化をオン (-O) にしなかったのでは? その結果、 gcc は asm/io.h の中にあるマクロを解決することができなかったのです。 それとも、#include <asm/io.h> を忘れてませんか? Q3. out*() を実行してもなにもおこらない、またはなんか変なのですが。 A3. パラメータの順番をチェックして下さい。outb(value, port) という 順番です。MS-DOS でおなじみの outportb(port,value) とは逆です。 I/O ポートアクセスをする別の方法 もう一つの方法は、open() を使って /dev/port (キャラクタデバイス、メジャー 番号 1、マイナー番号4)をオープンして、read/write をおこなうという方法です。 (stdio の f*()関数(訳注: fwrite() とか fread() とか...)は内部でバッファリング をしているので、使えませんね。) オープンした後に、入出力したいポートのアドレスまで lseek() します。 (file position 0 が ポート 0 に、file position 1 が ポート 1 ... といった 具合に相当します。) その後に read(), write() を使ってバイト/ワードを読み書きします。 この方法を使うには、もちろん そのプログラムが /dev/port に対する read/write 許可を持っていなければなりません。 この方法は上に述べた通常の方法よりもおそらく遅いでしょうが、(コンパイル時の) 最適化や ioperm() が不要であるという利点があります。 割り込み (IRQ) と DMA アクセス ユーザモードのプログラムから直接に割り込みや DMA を使うことはできません。 これには、カーネルドライバを作成する必要があります。その詳細については 「Linux カーネルハッカーズ ガイド」を、またその例としては、カーネルの ソースコードを読んで下さい。 また、ユーザモードプログラムの中で割り込みを禁止する手段もありません。 高い精度のタイミング制御: ディレイ まず最初に断っておきますが、ユーザプログラムにおいて正確なタイミング保証 をすることはできません。これは、Linux がマルチタスク・プリエンプティブ なシステムだからです。あなたのプロセスが、約20m秒から数秒(非常に負荷の 高いシステムなどで)に渡ってなんらかの理由でスケジューリング対象から 外されることは、いつでもあり得ます。 しかし、I/O ポートを使うほとんどのアプリケーションでは、これは実際には 問題にはならないでしょう。 この時間をできるだけ小さくするためには、nice を使って、プロセスの優先順位 を高くするということもできます。(訳注: スラッシングがおきているような状況 では nice の効果はほとんどないと思いますが、ただ単に負荷が高いような場合 (つまり、CPU リソースだけが足りない、という状況)には効果があるのかな。) Linux カーネルにリアルタイム機能を追加するという計画もあります。 これについては、http://luz.cs.nmt.edu/~rtlinux を御覧下さい。 さて、もっと簡単なタイミングについてお話しましょう。数秒のディレイが 欲しい場合には、おそらく sleep(3) がいいでしょう。数十ミリ秒以上の ディレイ(最小のディレイは約20ミリ秒のようです。)の場合には usleep(3) がお薦めです。 これらの関数を呼び出すと、CPU は他のプロセスに割り当てられますので、 CPU タイムが無駄になることはありません。 詳細については man ページを御覧下さい。 約 20 ミリ秒よりも小さいディレイに関しては(おそらくプロセッサやマシンの 速度、システムの負荷にも依存しますが)、上に述べたような CPU を放してしまう というアプローチではうまくいきません。通常、Linux のスケジューラがあなたの プロセスに対して制御を戻す前に少なくとも 20 ミリ秒はかかるからです。 この理由から、小さな遅延の場合には usleep(3) に指定した遅延よりもいくぶん 長い時間遅延することになります。また、指定するパラメータの最小値が 20 ミリ秒 であるのも、これが理由です。 短いディレイ(数十マイクロ秒から 20 ミリ秒)が必要な場合に一番簡単な 方法は、/usr/include/asm/delay.h (linux/include/asm-i386/delay.h)に定義 されている udelay() を使うという方法です。 udelay() に渡すパラメータは、何マイクロ秒ディレイするか、という値 (unsigned long)です。パラメータはひとつだけで、返り値はありません。 どれくらいディレイすべきかを計算するためのオーバーヘッドがあるため、 指定した値よりも数マイクロ秒余計にディレイしてしまう可能性があります。 (詳細は delay.h を参照のこと。) カーネル以外から udelay() を使うには、unsigned long の変数 loops_per_sec を 適切な値に設定しておく必要があります。 この値をカーネルから引っ張ってくる方法は、(私の知る限りでは)、/proc/cpuinfo を読みだして、BogoMips の値を知り、これに 500000 を乗じて、これを loops_per_sec の値にするという方法です。(不正確ですけど。) (訳注: Linux 2.0.20 の私の環境で cat /proc/cpuinfo すると、 processor : 0 cpu : 586 model : 7 vendor_id : GenuineIntel stepping : unknown fdiv_bug : no hlt_bug : no fpu : yes fpu_exception : yes cpuid : yes wp : yes flags : fpu vme de pse tsc msr mce cx8 bogomips : 40.03 となります。) Linux カーネル 2.0.x シリーズには、nanosleep(2) という新しいシステムコール が追加されました。これを使うと、その時間に応じて、スリープ/ディレイを 使い分けてくれます。現在の実装では、2ミリ秒以下のディレイの場合には udelay() を、それ以上の場合にはスリープするというふうになっているようです。 ですから、精度は相変わらず数マイクロ秒だと思われますが、さらに新しいカーネル では改善されるだろうと思います。 私は nanosleep(2) のドキュメントを見つけることができませんでしたが、 /usr/src/linux/kernel/sched.c を見る限り、その書式(呼び出し方)は: int nanosleep(struct timespec *rqtp, struct timespec *rmtp); のようです。 ここで、構造体 rqtp によって、どれだけ待ちたいかを指定します。 また、もし rmtp が NULL 以外の値で、nanaosleep() がアボートされた場合 には(errno 変数が EINTR に設定され、返り値は -1 となり)、残り時間が 構造体 rmtp の中に格納されます。 ディレイが正常に完了した場合には 0 が返され、なんらかの理由で完了しなかった 場合には -1 が返されます。(この場合には errno が適切な値に設定されます。) さらに短いディレイが必要な場合には、いくつか方法があります。 ポート 0x80 に出力(値はなんでもいいです。)すると、プロセッサの種類や 速度に関係なく、ほぼ正確に 1マイクロ秒待つことができます。(出力の方法 については、前の方を読んで下さい。)これを必要な回数繰り返すことで数マイクロ秒 待つことができます。標準的なマシンでは、このポート出力によってなにか変な 副作用があったりしないはず(カーネルドライバでこれを使っているものも ありますから)です。 この方法は {in|out}[bw]_p() でもディレイするために通常に使われている方法 です。(asm/io.h を見て下さい。) そのプログラムが走るマシンのプロセッサの種類とクロック速度がわかっている 場合には、特定のアセンブラ命令をハードコードすることで、もっと短いディレイ を実現することもできます。(しかし、プロセスがスケジューリングから外され、 ディレイが長くなってしまうことがあり得ることを忘れないで下さい。) 以下の表では、それぞれの命令で何クロック(内部クロック)消費されるかを示して います。これによってどれくらいの時間を消費するかを知ることができます。 例えば 50MHz のプロセッサ (486DX-50 とか、486DX2-50) の場合には 1 クロックは 1/50000000 秒です。 Instruction i386 clock cycles i486 clock cycles nop 3 1 xchg %ax,%ax 3 3 or %ax,%ax 2 1 mov %ax,%ax 2 1 add %ax,0 2 1 [出展: Borland Turbo Assembler 3.0 Quick Reference] (申し訳ないのですが、ペンティアムプロセッサについては私はわかりません。 おそらく、i486 に近いと思います。) (i386 で 1 クロックの命令はみつけられませんでした。) 上の表で、nop と xchg は何も副作用はないはずです。その他の命令はフラグ レジスタを変更する可能性があります。でも、gcc はそれを検出してくれます から、問題にはならないはずです。 これらを使うには、プログラムの中で asm("命令"); という書式を使って、 上の表の命令を埋め込みます。ひとつの asm() 文で複数の命令を埋め込む には、asm("命令; 命令; 命令"); という書式を使います。 asm() 文は、gcc によって、インラインアセンブラコードに変換されるので 関数呼び出しのオーバーヘッドはありません。 また、ペンティアムプロセッサでは、リブートから現在までの経過クロック サイクルを知ることができます。以下のコードです: extern __inline__ unsigned long long int rdtsc() { unsigned long long int x; __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x)); return x; } いずれにせよ、現在のインテル x86 アーキテクチャでは、1 クロックサイクル よりも短いディレイは不可能です。 高い精度のタイミング制御: 時間の測定 1 秒くらいの精度の時間ならば、time(2) を使うのが一番簡単でしょう。 もっと正確な時間が必要な場合には、gettimeofday(2) を使うと 大体、マイクロ秒 の精度があります。(でも、前の方で述べた、スケジューリングのことは忘れない でください。)ペンティアムプロセッサの場合には、上のコードを使うと 1 クロック サイクルの精度が出ます。 プロセスが、ある時間経過した後にシグナルを受け取りたい場合には、setitimer(2) を使います。この関数の詳細については man ページを御覧下さい。 その他のプログラミング言語について 以上の説明では C 言語に特化して述べましたが、C++ や Objective C でも 同じようにできるはずです。 アセンブラの場合、C の場合と同様に ioperm() やiopl() を呼び出さなければ なりませんが、その後には直接 I/O ポートに対するリード/ライト命令(訳注: in, out 命令)を使うことができます。 その他の言語で、もしインラインアセンブラや C のコードを埋め込むことが できない場合にもっとも簡単な方法は、必要な I/O ポートアクセスのための ソースコードを C で記述してコンパイルし、プログラムの他の部分とリンクして しまうという方法でしょう。 よく使われるポート 以下は、汎用 TTL 論理の I/O ポートについてのプログラミング情報です。 パラレルポート(ベースアドレス = /dev/lp0 は 0x3bc、/dev/lp1 は 0x378、 /dev/lp2 は 0x278): [出展: IBM PS/2 model 50/60 Technical Reference と、 実験結果から] (私は、比較的新しいポートでサポートされている ECP/EPP モード がどういうふうに動作するのかは知りませんが、以下で述べる(普通の)ポートは サポートされているはずです。) (通常のプリンタポートとして動作させたいだけでしたら、Printing-HOWTO を 読むのがいいでしょう。) ほとんどのパラレルポートには、標準的な出力専用モードに加えて、「拡張」 双方向モードがあります。このモードでは、方向ビットを設定することによって 同じポートを出力または入力に切替えることができます。 でも、私はどうやったら拡張モードをオンにできるのか知りません。 (デフォールトではオフのはずです。)...もし知っている方がいたらメイル下さい。 BASE + 0 (データポート) は、ポートのデータ信号を制御します。(D0 〜 D7 は ビット0 〜 7 に対応します。状態 : 0 = low (0V)、1 = high (5V))。 このポートに対するライトはデータをラッチして外部のピンに出力します。 標準または拡張ライトモードの場合には、リードすると、最後にライトされた データが読み出されます。拡張リードモードの場合には、外部ピンのデータが読み出 されます。 BASE + 1 のポート(ステータスポート) はリードオンリーで、以下のような 入力信号のステータスを返します: Bit 0 と 1 は予約されている Bit 2 IRQ ステータス (外部ピンの値ではない。どういうふうに動作するか知らない) Bit 3 ERROR (1=high) Bit 4 SLCT (1=high) Bit 5 PE (1=high) Bit 6 ACK (1=high) Bit 7 -BUSY (0=high) (high/low の割り付けに関してはあまり確かではありません。) BASE + 2 のポート(制御ポート)はライトオンリー(リードした場合、最後にライトした データを返す。)で、以下のステータス信号を制御します: Bit 0 -STROBE (0=high) Bit 1 AUTO_FD_XT (1=high) Bit 2 -INIT (0=high) Bit 3 SLCT_IN (1=high) Bit 4 '1' にすると、パラレルポートの割り込みを許可する(ACK の low -> high の 遷移で割り込み発生。) Bit 5 拡張モードでの方向(0 = 出力, 1 = 入力) を制御する。このビットは 完全にライトオンリーで、リード時には不定の値が返る。 Bit 6 と 7 は予約されている。 (繰り返しますが、high/low の割り付けに関してはあまり確かではありません。) ピン接続 (25 ピンの D-sub メスコネクタ) (i=input, o=output): 1 io -STROBE 2 io D0 3 io D1 4 io D2 5 io D3 6 io D4 7 io D5 8 io D6 9 io D7 10 i ACK 11 i -BUSY 12 i PE 13 i SLCT 14 o AUTO_FD_XT 15 i ERROR 16 o -INIT 17 o SLCT_IN 18-25 グランド IBM の規格によれば、1, 14, 16, 17 番ピン(制御出力)は、4.7kオームで 5V に プルアップされたオープンコレクタドライバ (シンク(吸い込み) 20mA, ソース (吐き出し) 0.55mA, ただし、ハイレベルの出力は プルアップの 5V) その他のピンについては、シンク(吸い込み) 24mA, ソース(吐き出し) 15mA, ハイレベル出力は最低でも 2.4V, ローレベルの出力は最大でも 0.5V。 IBM 以外のパラレルポートではおそらくこの標準とは少々違う場合もあるでしょう。 最後に注意: グランド(接地)には気をつけましょう。私はコンピュータが稼働中に コネクタを接続したせいで、パラレルポートを壊してしまった経験があります。 これを考えると、マザーボードに乗っているのではない(拡張カード上の)パラレル ポートを使うのがいいかもしれませんね。 (訳注: 各機器のフレームグランドは、ちゃんととりましょうね。) ゲーム(ジョイスティック)ポート (ポート 0x200-0x207): (普通のジョイスティック を制御するには、カーネルレベルのジョイスティックドライバがあります。 ftp://sunsite.unc.edu/pub/Linux/kernel/patches/joystick-* を御覧下さい。) ピン接続 (15 ピンの D-sub メスコネクタ): 1, 8, 9, 15: +5 V (電源) 4, 5, 12 : グランド 2, 7, 10, 14: デジタル入力 (順番に) BA1, BA2, BB1, BB2 3, 6, 11, 13: アナログ入力 (順番に) AX, AY, BX, BY +5 V ピンはマザーボードの電源に直接接続されているようですので、 (マザーボードの作りや、電源にも依存しますが)非常に多くの電力を 供給することができるはずです。 このポートには二つのジョイスティックを接続できますが、 デジタル入力は、これらのボタンからの入力です。(ジョイスティック A と ジョイスティック B それぞれに二つずつボタンがあります。) これらは通常の TTL レベルの入力で、以下で述べるステータスポートから 現在の状態を読み出すことができます。 ジョイスティックのボタンが押されていると low (0V) を、そうでなければ high (1K オームの抵抗を通じて 5V 電源にプルアップ) が読み出されます。 いわゆるアナログ入力というのは、実際には抵抗値を計っているのです。 以下でその仕組みを述べます。 ゲームポートには4本の入力それぞれにワンショットマルチバイブレータ が接続されています。(全体(4本)で 558 チップ一つ) それぞれの入力について、入力ピンとマルチバイブレータの出力の間に 2.2K オームの抵抗が、マルチバイブレータの出力とグランドの間に 0.01uF のタイミング用コンデンサが接続されています。実際のジョイスティックには、 X と Yの両方の軸にそれぞれポテンショメータがあり、+5V とそれぞれの 入力ピンに接続されています。(AX, AY がジョイスティック A に、 BX, BY がジョイスティック B に対応します。) (訳注: 回路図はこんな感じかな: +5V | ^ | / 2.2Kオーム +---/\/\/\--IN----/\/\/\------+ / | ポテンショメータ | +---------+ | | Multi | | | vib. Q|--+ | | | | | | | | | | | === 0.01uF | | | +---------+ | | GND ) マルチバイブレータが作動すると、その出力を high (5V) にドライブし、 それぞれに接続されているタイミングコンデンサの電位が 3.3V になるまで 待ち、その後に出力を low にします。この結果、マルチバイブレータの出力が high の時間はジョイスティック中のポテンショメータの抵抗値に比例します。 (つまり、それぞれの軸のジョイスティックの位置を表します。) 抵抗値は以下の式のとおりです: R = (t - 24.2) / 0.011, ここで、R は ポテンショメータの抵抗値(オーム)、t は high の時間(秒) です。 とまあ、こういうわけですので、アナログ入力を読み出すためには、以下で 述べるようにまずポートにライトして、マルチバイブレータを作動させる必要が あります。 その後に4つの軸の状態をポーリング(ポートを繰り返し読み出す)し、 それぞれのポートが high から low になるまでの時間を測定します。 このポーリングには非常に CPU タイムを消費しますし、Linux のような 非リアルタイム・マルチタスクシステムではその結果はあまり正確ではありません。 ポートを絶え間なく読み出すことはできないからです。(カーネルレベルのドライバ を使うか、ポーリングの間割り込みを禁止すればいいのですが、これは更に多くの CPU タイムを食います。) さて、アクセスする必要のあるポートはただ一つ、0x201 (他のポートアドレスはこの ポートと同じようにふるまうか、なにもしないか、のどちらか)です。 このポートに対するライト(書き込むデータはなんでも良いです。)によって マルチバイブレータが作動します。リードすると入力信号の状態が返ります: Bit 0: AX (マルチバイブレータ出力の状態 (1=high)) Bit 1: AY (マルチバイブレータ出力の状態 (1=high)) Bit 2: BX (マルチバイブレータ出力の状態 (1=high)) Bit 3: BY (マルチバイブレータ出力の状態 (1=high)) Bit 4: BA1 (デジタル 入力, 1=high) Bit 5: BA2 (デジタル 入力, 1=high) Bit 6: BB1 (デジタル 入力, 1=high) Bit 7: BB2 (デジタル 入力, 1=high) まともなアナログ I/O が必要な場合には、ADC (アナログ -> デジタル コンバータ) や DAC (デジタル -> アナログ コンバータ)チップをパラレルポートに接続するという 方法もあります。(ヒント: 電源には気をつけて下さい。ゲームポートからの電源を 使うか、ディスクドライブ用の余っている電源コネクタをコンピュータケースから 引っ張り出すなどがいいでしょう。パラレルポートから電源をとるならば、低消費電力 のデバイスを使う必要があります。)(訳注: いずれにせよ、これらはデジタル系の電源 ですから、アナログ系に使う場合にはなんらかの方法でデカップリングをお忘れなく。) または、AD/DA カードを購入する(ほとんどは I/O ポートで制御できます。)という 手もあります。 もし、1〜2チャネルで十分であり、あまり正確でなくてもよく、(おそらく) 不正確な中点補正(zeroing)でもよければ、Linux のサウンドドライバ でサポートされている安いサウンドカードを買ってきて使う、という方法もあります。 (それに、結構高速ですよね。) プログラムの例 これは、I/O ポートアクセスの単純なコーディング例です: (訳注: 参考までにコメントの部分を訳出していますが、gcc で日本語コメント が使えるかどうか、訳者は確認していません。) /* * example.c: とっても簡単な ポート I/O の例 * * なにも役にたつことはしていません。ポートに書き込み、一時停止して、 * ポートから読みだすだけです。「gcc -O2 -o example example.c」 * というコマンドラインでコンパイルして下さい。 */ #include <stdio.h> #include <unistd.h> #include <asm/io.h> #define BASEPORT 0x378 /* lp1 */ int main() { /* ポートへのアクセス権限を得る */ if (ioperm(BASEPORT,3,1)) {perror("ioperm");exit(1);} /* ポートのデータ (D0-D7) をオール low (0) にする */ outb(0,BASEPORT); /* しばらく (100000us = 100ms) スリープする */ usleep(100000); /* ステータスポート (BASE + 1) から読み出して、それを表示する */ printf("status: %d\n",inb(BASEPORT+1)); /* もうポートを使わないので後始末 */ if (ioperm(BASEPORT,3,0)) {perror("ioperm");exit(1);} exit(0); } /* end of example.c */ 謝辞 とてもたくさんの方から助け (contribute) を受けましたので、ここには記しきれ ません。皆さん、とてもありがとう。 すべての方にリプライすることはできませんでしたがそういう方々には ごめんなさい、そしてもう一度、助けてくれてありがとう。 End of the Linux I/O port programming mini-HOWTO.