デバイススペシャルファイル

提供:UnixClassWiki
2015年7月27日 (月) 15:50時点におけるHironobu (トーク | 投稿記録)による版 (→‎I/Oの抽象化)
ナビゲーションに移動 検索に移動

第七章

デバイススペシャルファイル

I/O の抽象化

Unix で使われた秀逸なアイデアを3つ上げろといわれたら、多分、多くの人が、階層化ファイルシステム、パイプ、そしてデバイススペシャルファイル(単にスペシャルファイルとも呼ぶ)をあげるでしょう。今回はその中の2つに関係しています。 デバイスを抽象化するこのアイデアによって、 I/O のデバイスもすべてファイルと同じ統一したインタフェースで扱えるようになりました。 例えばハードディスクや端末といったものに対して、プロセスから直接ハードウェアにアクセスする必要はありません。 I/O のスペシャルファイルを経由してアクセスします。 ディレクトリ /dev 以下に用意されているスペシャルファイルがデバイスへのインタフェースです。


端末もスペシャルファイルとして抽象化されていて、たとえば現在使っている端末は /dev/tty のように見えます。 ですから、ここに文字をリダイレクトするとスクリーンにその文字が出力されます。

 $ echo 'hello' > /dev/tty
 hello


たとえばLinuxではIDEハードディスクが使えるコンピュータだとIDE 0 Masterに接続さ れているハードディスクは/dev/hdaになります。Slave側はhdb、IDE 1 Master/Slaveはhdcとhddという具合になります。/dev/hdaはハードディスク全 体で、hda上にパーティションが設定されていればhda1、hda2...が各々のパー ティションを指しています。/dev/sdaはSCSIハードディスク、あるいはSCSIハー ドディスクに見えるものです。必ずしも/dev/sd?はSCSIハードディスクではな くSCSIのインタフェースが使えて、かつブロックデバイスであればかまいませ ん。実際に接続されているのは、コンパクトフラッシュメモリだったり、USB 接続の外部HDDだったり、あるいはIDE接続のMOがide-scsiデバイスドライバ経 由のためSCSI として取り扱われたりと状況でさまざまです。


補足
接続した順番にデバイスを決めていくので、USB機器などの接続では挿したタイミングでデバイス名が異なることが発生してしまう場合があります。そこで現在ではUUID(Universally Unique Idenifier )という方式を使ってデバイスをユニークに認識する方法を用意しています。fstab(5)、mount(8)、tune2fs(8)を参照してみてください。


/dev/cua0や/dev/ttyS0はシリアルポートのスペシャルファイルです。cua?は たとえばモデムやFAXモデムを接続しておいて、自分から呼び出して接続を行 う(Call-Out)する時に使います。ttyS?は相手から呼び出される時に使います。

レイヤ図

プログラムは、スペシャルファイルやシステムコールからカーネルを経由し、最後はカーネル内にあるデバイスドライバを経由してハードウェアにアクセスします。下に簡単なレイヤー図を載せます。

Device Driver Layer

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です。

ハードディスクなどのデバイスからブロックの内容が読み込まれたとき(ある いは書き込まれる途中)、データはメモリ中のバッファの中に保管されていま す。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[1]、 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に関する考察 [2]

ネットワークデバイス

1981年当時4.1BSDを改造しTCP/IPのスタックを搭載したのがUNIXのTCP/IPの始まりです。 私の個人的経験でいわせてもらうと4.2BSDリリース時に入っていたTCP/IPはどう贔屓目に見ても、安定して利用するというには程遠く、プログラム中で引数をちょっと間違えるとシステム全体がいとも簡単にダウンしました。 安定して使えたという実感は4.3BSDになってからです。


当時、我々がLANと呼ぶものはイーサーネット(Ethernet)で構築されていました。NIC (Network Interface Card)とか、あるいはLANポートとか呼ぶものも、 使っているのはイーサーネットでした。そのため/dev/eth0といった名前でネットワークのデバイスファイルが作成されました。 むかしのLinuxもイーサーネットのデバイスは、もちろんUnix流に/dev/の下にあるデバイススペシャルファイルで、デバイスは/dev/eth0と見えていました。


補足
eth0は最初に認識しているイーサネットのネットワークデバイスで、複数のネットワークポートやNICが存在していた場合、eth1、eth2...となります。


ところが今の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はループバック


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)

脚注

  1. 最新ディストリビューションの使っているLinuxカーネルの多くでは既に用意していません。
  2. Linux disk I/O considerations http://public.dhe.ibm.com/software/dw/linux390/perf/Linux_disk_IO_considerations.pdf

目次