デバイススペシャルファイル
第七章
デバイススペシャルファイル
I/O の抽象化
Unix で使われた秀逸なアイデアを3つ上げろといわれたら、多分、多くの人が、階層化ファイルシステム、パイプ、そしてデバイススペシャルファイル(Device Special File : 単にスペシャルファイルとも呼ぶ)をあげるでしょう。今回はその中の2つに関係しています。 デバイスを抽象化するこのアイデアによって、 I/O のデバイスもすべてファイルと同じ統一したインタフェースで扱えるようになりました。 例えばハードディスクや端末といったものに対して、プロセスから直接ハードウェアにアクセスする必要はありません。 I/O のスペシャルファイルを経由してアクセスします。 ディレクトリ /dev 以下に用意されているスペシャルファイルがデバイスへのインタフェースです。
端末もスペシャルファイルとして抽象化されていて、たとえば現在使っている端末は /dev/tty のように見えます。
ですから、ここに文字をリダイレクトするとスクリーンにその文字が出力されます。
$ echo 'hello' > /dev/tty hello
たとえばLinuxでは /dev/sda は SCSI ハードディスク、あるいは SCSI ハードディスクに見えるもの[1]です。
Linuxのカーネルが認識した順番に /dev/sda から /dev/sdb 、 /dev/sdc となります。
/dev/sda はハードディスク全体で、 sda 上にパーティションが設定されていれば /dev/sda1 、 /dev/sda2 ...となります。
必ずしも /dev/sd? はSCSIハードディスクではなくSCSIのインタフェースが使えて、かつブロックデバイスであればかまいません。
実際に接続されているのは、コンパクトフラッシュメモリだったり、USB接続の外部HDDだったり、
あるいはIDE接続のMOがide-scsiデバイスドライバ経由のためSCSI として取り扱われたりと状況でさまざまです。
IDEハードディスクだと IDE 0 Master に接続されているハードディスクは /dev/hda になります。
Slave側は hdb、 IDE 1 Master/Slave は hdcと hdd という具合になります。
- 補足
- 接続した順番にデバイスを決めていくので、USB機器などの接続では挿したタイミングでデバイス名が異なることが発生してしまう場合があります。そこで現在ではUUID(Universally Unique Idenifier )という方式を使ってデバイスをユニークに認識する方法を用意しています。fstab(5)、mount(8)、tune2fs(8)を参照してみてください。
/dev/cua0 や /dev/ttyS0 はシリアルポートのスペシャルファイルです。
cua?はたとえばモデムやFAXモデムを接続しておいて、自分から呼び出して接続を行う(Call-Out)する時に使います。
ttyS?は相手から呼び出される時に使います。
/dev/ttyUSB0 はUSB接続をしたシリアル通信のためのスペシャルファイルです。
レイヤ図
プログラムは、スペシャルファイルやシステムコールからカーネルを経由し、最後はカーネル内にあるデバイスドライバを経由してハードウェアにアクセスします。下に簡単なレイヤー図を載せます。
ioctl
ioctl(2)はスペシャルファイルをコントロールしているデバイスドライバに対して命令を送るためのシステムコールです。これでデバイスをコントロールすることになります。(執筆中)
デバイス
キャラクタデバイス
キャラクタデバイスは、時系列でデータが発生する端末、オーディオ、モデムあるいはテープ装置といったシーケンシャルにバイト単位でアクセスするような入出力を行うデバイスドライバです。 データはキャッシュしません。
$ ls -l /dev/{random,tty,st0,midi0} crw-rw---- 1 root audio 35, 0 Mar 15 2002 /dev/midi0 crw-rw-rw- 1 root root 1, 8 Dec 3 2002 /dev/random crw-rw---- 1 root tape 9, 0 Mar 15 2002 /dev/st0 crw-rw-rw- 1 root tty 5, 0 Nov 18 11:35 /dev/tty
先頭のcがキャラクタデバイスを意味する
ブロックデバイス
ブロックデバイスは、ハードディスクのようなランダムアクセスができ、かつ入出力がブロック単位でアクセスを行うことができるハードウェアへのデバイスドライバです。 データをキャッシュするので効率良く入出力ができます。
$ ls -l /dev/hd? brw-rw---- 1 root disk 3, 0 Mar 15 2002 /dev/hda brw-rw---- 1 root disk 3, 64 Mar 15 2002 /dev/hdb .... brw-rw---- 1 root disk 34, 0 Mar 15 2002 /dev/hdg brw-rw---- 1 root disk 34, 64 Mar 15 2002 /dev/hdh
先頭のbがブロックデバイスを意味する
効率が良いと書きましたが、正確には「効率良く入出力するための工夫をしている (だから効率が良い)」といった方がいいでしょう。
1つのブロックのことをセクタと呼び、普通はサイズは512バイトです。CD-ROM のようなデバイスでは最近は2KBですが、いずれにしてもセクターは2のN乗倍(512は2 の9乗、2Kは2の11乗)の値を取ります。
最大値は記憶管理のページのサイズ以下です。
32ビットアーキテクチャーなら4KB、64ビットアーキテクチャーであれば8KBです[2]。
ハードディスクなどのデバイスからブロックの内容が読み込まれたとき(あるいは書き込まれる途中)、
データはメモリ中のバッファの中に保管されています。1つのバッファは1つのブロックに対応しています。
普通はページサイズが4KBでブロックサイズが512 バイトですから、一つのページは複数のバッファから出来ています。
ブロックデバイスから幾つかのブロックでデータを読み込んで来た時(あるいは書き込もうとした時)、
メモリ中にバッファのチェーンが作られます。
このように、ブロックデバイスを読み書きする時には、ハードディスクのようなデバイスを直接読み書きするのではなく、
まずこのバッファに対して読み書きが行われます。
擬似デバイス
擬似デバイス (Pseudo-devices) とはデバイスファイルのように見せかけているが、その先には具体的なハードウェアが結びつけられていないデバイスファイルです。 たとえば /dev/null は、その先が何もないデバイスファイルです。
% cat foo > /dev/null
ファイル foo を読み込んで、デバイスファイル /dev/null に送り込みますが、送り先は何もないので /dev/null に吸い込まれるだけになります。
/dev/null を入力にした場合、何も送られないことになります。以下にいくつかのデバイスファイル例をあげます。
- /dev/null 入力・出力とも何もしない
- /dev/random , /dev/urandom 乱数を返す
- /dev/zero ゼロを返す
I/Oスケジューラ
さてまず、バッファに読み書きされることはわかりました。しかしハードディスク、あるいはそれに相当する具体的なブロックデバイスに書き込む、あるいは読み込む必要があります。 ハードディスクを例に取ると、円盤の磁性体が回っていて、そこに読み書きするヘッドが移動して、そして始めて読み書きが始まります。そのヘッドの移動のことをシークといいますが、ハードディスクのシーク時間は数ミリ秒程度かかります。数ミリ秒というと、とても短い時間のように思えますが、CPUの処理時間から比べれば長い長い時間です。
そこで有効な入出力をするためにスケジューラを用意します。スケジューラの役目は、全体のスループットの改善です。ですから、ある1つのプロセスだけを着目してみると、もしかすると、処理が遅くなっているという可能性もあります。
この当たりは単純に一つのI/Oスケジュリングのアルゴリズムが万能とはなかなかいかないので、Linux 2.6.0以降では
Deadline I/O Scheduler、
Anticipatory I/O Scheduler[3]、
Complete Fairness Queueing I/O Scheduler、
Noop I/O Scheduler といった複数
のスケジュールが用意されています。
- 補足
- Kernel 2.6.18以降 CFQ(Complete Fairness Queueing)スケジューラがデフォルトです。
- 補足
- Noop I/O Schedulerはスケジュールをしないスケジューラです。
- 調べてみよう
- ブロックデバイスが/dev/sdaの場合、I/Oスケジュラーの設定は /sys/block/sda/queue/scheduler でされているので、実際に、どうなっているか見てみよう。
- 調べてみよう
- IBMサイトにあるLinuxのディスクI/Oに関する考察 [4]
ネットワークデバイス
1981年当時4.1BSDを改造しTCP/IPのスタックを搭載したのがUNIXのTCP/IPの始まりです [5] 。 当時のLANも多くの場合イーサーネット(Ethernet)で構築されていました。 NIC (Network Interface Card) とか、あるいは LANポート と呼ぶネットワークインタフェースのためのデバイスがあり、 そのデバイスファイルとして /dev/eth0 が作成されました。
- 補足
- eth0は最初に認識しているイーサネットのネットワークインタフェースのデバイスで、複数のネットワークポートやNICが存在していた場合、 eth1、 eth2... となります。
むかしのLinuxもイーサーネットのデバイスは、もちろんUnix流に /dev/ の下にあるデバイススペシャルファイルで、
デバイスは /dev/eth0 と見えていました。
ところが今日のLinuxはネットワークデバイスに対してはスペシャルファイルとして用意していません。
イーサーネットデバイスのはずである/dev/eth?というのはLinux 2.2以降なくなりました。
理由は単純に1つのイーサーネットのデバイスが、1つのIPアドレスを持つというわけではなくなったからです。
現在はハードウェアに1つのイーサーネットのポートしかなくても、
オペレーティングシステムとして、その1つのポートに複数のIPアドレスを割り当てることができます。
ネットワークインタフェースの設定にはifconfigを使いますが、通常はシステムの設定ファイルに指定のフォーマットで登録しておけば、システムのブート時に設定スクリプトが動き、自動的に割り当ててくれます。Debian系のディストリビューションだと /etc/network/interfaces に、RedHat 系だと /etc/sysconfig/network-scripts/ifcfg-eth0 に記述します。
ネットワークインタフェースの設定状況を見るのには次のようにします。
$ /sbin/ifconfig eth0 Link encap:Ethernet HWaddr 00:C0:26:28:12:C5 inet addr:192.168.100.100 Bcast:192.168.100.255 Mask:255.255.255.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 .... lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 ....
- 補足
- loはループバックのための仮想デバイスです。127.0.0.1は特別なIPアドレスで、これは自分自身を指します。
ifconfigを使う以外にも/proc/net/dev/を見ることで現在のネットワークインタフェースのデバイスの状況を確認することができます。
$ cat /proc/net/dev Inter-| Receive .... face |bytes packets errs drop fifo frame ... lo: 321508 2757 0 0 0 0 ... wlan0: 0 0 0 0 0 0 ... eth0: 1087299 17929 0 0 0 0 ...
udev hald
ホットプラグを実現するために動的に作られるデバイス(TBD)
脚注
- ↑ たとえば USB メモリは USB マスストレージクラスですが、デバイス用の SCSI エミュレーションによってSCSIデバイスのように見えます。
- ↑ これは典型的な例で、ページサイズはハードウェアのアーキテクチャーに依存します。
- ↑ 最新ディストリビューションの使っているLinuxカーネルの多くでは既に用意していません。
- ↑ Linux disk I/O considerations http://public.dhe.ibm.com/software/dw/linux390/perf/Linux_disk_IO_considerations.pdf
- ↑ 私の個人的経験でいわせてもらうと4.2BSDリリース時に入っていたTCP/IPはどう贔屓目に見ても、安定して利用するというには程遠く、プログラム中で引数をちょっと間違えるとシステム全体がいとも簡単にダウンしました。安定して使えたという実感は4.3BSDになってからです。
目次へ
このページへのショートURL:
http://uc2.h2np.net/i/2d.html