• 作成:

UNIX課題 ファイルとしてのインタフェース

/proc/以下のようにシステムに作用させるインタフェースなのにファイルのように見せているインタフェースを用意している。一方でファイルにアクセスする形式ではなくWindowsプログラミングAPIのように関数へのインタフェースに統一するアプローチもある。自分なりに考えて前者の利点、後者の利点を説明せよ。

ファイルのように見せているインタフェースの利点

どの言語でも容易にアクセスが可能

ファイルを読み込めればアクセスが可能となるため, ファイルをopen, read, writeすることさえできれば, 複雑なシステムコールを取り扱ったり, 関数をラップする必要がありません.

LinuxカーネルはC言語で書かれていますし, 多くのツールもC言語で書かれてきましたが, 後年パフォーマンスを必要としない動作には, bash, sed, awk, perl, python, rubyなどの多種多様なプログラミング言語が利用されてきました.

茸のように増えていくプログラミング言語全てにインターフェイスを提供するのは大変ですし, C言語関数を呼び出すのも面倒です.

そこでファイルとしてインターフェイスを提供しておけば, プログラミング言語側ではファイルを取り扱うインターフェイスだけを用意すれば良いことになります.

コマンドラインツールを組み合わせるのが容易

ファイルとしてインターフェイスが扱えるということがシステムの共通認識となっていれば, コマンドラインツールを書くことが容易になります.

実例を上げます. 私は指定されたファイルを指定されたネットワーク上にアップロードするコマンドラインツールを書く仕事をしたことがありました.

そのソフトウェアは要件として, 他のツールと協調動作することが必要でした.

そこで, 単独動作するときは引数に指定されたファイルをアップロードして, 協調動作するときは標準入力の内容をアップロードすることが求められていました.

このソフトウェアがWindowsなどで動く必要があるならば, 標準入力の内容を読み取るモードを実装する, 協調ソフトウェア側でtmpファイルを作成する, と言った対処が必要でした. しかし, このソフトウェアの動作する環境はUbuntuでした.

そこで私は協調動作するときは/dev/stdinを引数に渡すように, 協調するツールを実装して, 2つのモードを実装する手間を省き, 楽に仕事を終えることができました.

このようなことは多くあり, これこそがWindowsでコマンドラインツールを組み合わせる文化が産まれにくい原因ではないでしょうか.

ファイルが備えている機能を既に実装してある

ファイルはディレクトリ構造でグループ化出来て, ファイルハンドラによる排他制御ができ, ユーザーの権限を持ちます.

変数として考えてみると, このような豪華な変数は殆どのプログラミング言語は用意していないでしょう. クラスライブラリなどによって実現します.

ファイルを生やすだけで, このような多彩なAPIが出てきてくれます.

関数へのインターフェイスに統一するアプローチの利点

必要のないAPIを提供しないで済む

私はEmacsを使っているのですが, Emacs 24に付属するtramp-sh.elにはシェルの履歴を取り扱う機能にバグがあり, 環境や挙動によって/dev/nullを削除してしまうという不具合が存在していました. #19731 - 24.4; /dev/null is deleted by tramp-sh.el - GNU bug report logs 現在のバージョンEmacs 25ではこの挙動は修正されています.

/dev/nullが削除されてしまうと, このファイルに依存するプログラムは正常に動かなくなります.

なので, 私は/dev/nullを再作成するaliasを作って, このバグが発生するとしばらく手動で実行していました.

% type mk-dev-null
mk-dev-null is an alias for sudo rm /dev/null && sudo mknod -m 666 /dev/null c 1 3

これは一例ですが, このようにプログラムの不具合が発生するとファイルは削除されてしまったり, 不正なものが作成されてしまったり, 専有されてしまうことがあります.

通常の権限では擬似ファイルへの書き込みは基本的に禁じられているため, このようなことが起きることは無いでしょう. しかし, 今述べたようにroot権限を使用して動くプログラムにバグがあり, そのプログラムが擬似ファイルの取り扱いに失敗すると, このような惨事が発生してしまいます.

もちろん, 関数を取り扱ったアプローチでも, 誤ったプログラムは悲惨な結果を比き起こすでしょうが, インターフェイスがファイルだと, 本来必要のない動作を提供してしまうという問題があります. 例えば, /dev/nullという標準出力を消すという機能に, その機能を無効化するというインターフェイスは不要でしょう. しかし, インターフェイスがファイルであるために, ファイルを削除するという機能を無駄に提供してしまっています.

APIの存在がわかりやすい

Linuxでファイルを使ってマウスにアクセスする場合, 私の環境では/dev/input/mouse0へアクセスすることになります. そこで, タイプミスなどをしてファイル名を間違えてしまった場合, コンパイラはそれを検出することができません.

いや, わかっています, 私だってLinuxでマウスにアクセスする時に/dev/input/mouse0にファイルアクセスなんてしません. けれどこれはファイルによるAPIとの比較例なのでそこは目を瞑っていただきたいです.

しかし, Windowsの.NET Framework クラス ライブラリではマウスはC#ではSystem.Windows.Input.Mouseの静的クラスとして定義されています. Mouse クラス (System.Windows.Input) なので, コンパイラはちゃんとマウスにアクセスしていることを認識することが可能です.

また, 特定のデバイスが存在しない時も, ファイルによるアクセスではプログラムは「ファイルが存在しない」というメッセージだけを受け取り, エラーメッセージを自作する必要がありますが, クラスライブラリを使っていれば, ある程度は状況に適したエラーメッセージが表示されることでしょう.

APIに型をつけることができる

ファイルインターフェイスでは, データ構造に型を付けることができません. 何を言っているのかというと, PowerShellの話をしています.

先ほどC#での.Net Frameworkライブラリへのアクセスでコンパイルするときに間違いが検出されると述べましたが, 型による恩恵はコンパイルする言語だけにあるわけではありません.

PowerShellは.Net Frameworkライブラリにアクセスすることが出来て.Netオブジェクトにアクセスすることが可能です. テキストファイルベースのAPIでは複雑なテキスト処理が必要なことも, オブジェクトならプロパティにアクセスするだけで可能だったりします.

これは, テキストファイルをベースとしたAPIでは出来ないことです. プロパティアクセスなどはJSONにフォーマットを統一するなどで可能ですが, メソッドを用意することなどは難しいでしょう. fuseを使ってシステムを構築する試みなどはありますが, 遊びの域でしょう.

結論

Linux(POSIX)にも例えばキーボードの入力を取得する関数があったりするわけですし, Linuxの場合は使い分けが可能なので, 好きな方法を使えば良いと思います.

Windowsのみで開発するときはやはり.Netの長い縄に巻かれるのが手っ取り早いと感じました.

参考文献

本文にリンクを貼られていない参考文献をここに列挙します.