|
||||||
ホーム / UNIX, X Window 環境の概念や小技 | ||||||
UNIX, X Window 環境の概念や小技
|
* 更新情報
オープンソース化されたバージョンの UNIX (Linux や BSD)が普及してきました。以前であれば UNIX は大学の情報系の学科の占有物とすら言える状態でしたが、今では一般の人にも触れることができます。一方では、以前であれば UNIX が主たる計算機環境だった大学では UNIX についての教育に重きを置かなくなっているようです。 ここでは、UNIX, X Window を使う上で必要となる概念や小技について解説をします。 UNIX の UNI は UNIX が意識した Multics というOSへのアンチテーゼです。Multics は学会と企業が共同でつくった1960年代の次世代OSで、沢山の機能。つまり、マルチな機能を売りにしていました。しかし、そのプロジェクトは技術的には成功しましたが、Multics が普及することはありませんでした。それをあざ笑うかのようにたった二人の研究者がシンプルなOSを作り上げ、あっという間に普及しました。それが UNIX です。 UNIX は OS が提供すべき最小限の機能は何であるか、を追及しました。Multics は要求される機能があれば、その数だけ機能が実現されるというものですが、UNIX は要求される機能を実現するために最低限必要な機能は何であるか、というのがテーマです。何しろたった二人でC言語のコンパイラからOSのカーネルからデバイスドライバ、シェルやコマンドま作り上げるのですから無駄なものを作る余裕はありません。 UNIX には最小限の機能しか提供されていませんので、利用者は最小限の機能を組み合わせて目的を達成することになります。つまり、UNIX を利用するということは、事実上プログラミングをしているわけです。Windows のように要求される機能には対応する機能が提供されていて、それを選ぶことが操作すること、というのとはずいぶん違う世界です。 プログラミングという行動を身につける上では、普段の操作全てがプログラミングである UNIX に慣れることは、実力をつける上で有利です。 OS の上でプログラムを動かす方法はいくつかありますが、最低でもユーザーからプログラム起動要求を受け付ける、プログラムが必要です。そのようなプログラムは OS と一緒にインストールされていて、一般的にシェルと呼ばれます。シェルについて次項で書きます。 シェルとユーザーとの対話の方法には、グラフィック・ディスプレイとポインティング・ディバイスを多用するグラフィカル・ユーザ・インタフェイス(GUI)と文字のシーケンシャルな入出力によるコマンドライン・ユーザー・インタフェイス (CUI) スがあります。どちらにもネットワークを通してリモートから操作する方法が用意されています。 ユーザーインタフェイスを動かすハードウェアが、計算機(CPUとメモリが入ったメインの箱)とは独立したハードウェアとして実現されている場合、それを端末 (Terminal) と呼びます。簡単な機能の端末であれば、CPU が入っていない場合もありますが、いまどきではどんな端末でもある程度の計算能力が必要となるので、ターミナル自体も計算機です。 端末の機能を汎用の計算機の上で実現する為のソフトウェアもターミナルと呼ばれます(なぜか、ソフトの場合は端末ではなくターミナルと呼ばれる)。 端末と計算機の間の接続は普通はケーブル(規格としては RS-232C が使われることが多い)でつながれます。しかし、ネットワーク技術の進歩で直接的物理的な接続ではなく間接的論理的なネットワーク経由の接続も可能になりました。それらは仮想端末 (Virtual Terminal) と呼ばれます。いまでは、こっちの方が普通です。 Windows にも UNIX にも GUI、CUI 両方のシェルがあります。しかし、Windows では GUI シェルが中心。UNIX の場合は CUI シェルが中心です。 Windows のシェルは Explorer です。UNIX 上のシェルはプログラムの名前自体が sh (shell の頭2文字)です。sh は一番基本的なもので、普通は各種の機能拡張が施されたシェルが使われます。 Explorer と sh の一番の違い以下の二つの点です。 文法: 機能: まず、基本的な癖をつけておいてください。必ず、コマンドラインのオプションにはどんなものが有るか確認しましょう。コマンドにオプションをつけないで起動するか、あるいは 例えば、(GNU) C コンパイラの場合、何も引数を付けないで起動すると mobius1@flanker$ gcc gcc: No input files specified mobius1@flanker$ と、入力ファイル(Cのソース)の指定が無いと文句を言われます。そこで、一つでもソースコードを指定してあげます。 mobius1@flanker$ gcc helloworld.c mobius1@flanker$ と黙って終ります。コンパイルの結果、実行可能ファイル 一方、何もコマンドラインオプションを指定しないと、止まってしまうプログラムもあります。 mobius1@flanker$ cat これは、止まっているけど、別にフリーズしたり無限ループに入っているのではなくて、標準入力からの入力を待っているのです。UNIX 上の文化として、標準入力をデフォルトの入力とするプログラムは、コマンドラインオプションを指定しないと標準入力を待って止まります。 一方、標準入力をデフォルトの入力としない(できない)プログラムは、コマンドラインオプションが無いと、先の mobius1@flanker$ javac Usage: javac <options> <source files> where possible options include: -g Generate all debugging info -g:none Generate no debugging info -g:{lines,vars,source} Generate only some debugging info -nowarn Generate no warnings -verbose Output messages about what the compiler is doing -deprecation Output source locations where deprecated APIs are used -classpath <path> Specify where to find user class files -cp <path> Specify where to find user class files -sourcepath <path> Specify where to find input source files -bootclasspath <path> Override location of bootstrap class files -extdirs <dirs> Override location of installed extensions -endorseddirs <dirs> Override location of endorsed standards path -d <directory> Specify where to place generated class files -encoding <encoding> Specify character encoding used by source files -source <release> Provide source compatibility with specified release -target <release> Generate class files for specific VM version -version Version information -help Print a synopsis of standard options -X Print a synopsis of nonstandard options -J<flag> Pass <flag> directly to the runtime system prprt@flanker$ 明示的に助けを求める場合は、コマンドラインオプションに mobius1@flanker$ gcc --help Usage: gcc [options] file... Options: -pass-exit-codes Exit with highest error code from a phase --help Display this information --target-help Display target specific command line options (Use '-v --help' to display command line options of sub-processes) -dumpspecs Display all of the built in spec strings -dumpversion Display the version of the compiler -dumpmachine Display the compiler's target processor -print-search-dirs Display the directories in the compiler's search path -print-libgcc-file-name Display the name of the compiler's companion library -print-file-name=<lib> Display the full path to library <lib> -print-prog-name=<prog> Display the full path to compiler component <prog> -print-multi-directory Display the root directory for versions of libgcc -print-multi-lib Display the mapping between command line options and multiple library search directories -print-multi-os-directory Display the relative path to OS libraries -Wa,<options> Pass comma-separated <options> on to the assembler -Wp,<options> Pass comma-separated <options> on to the preprocessor -Wl,<options> Pass comma-separated <options> on to the linker -Xassembler <arg> Pass <arg> on to the assembler -Xpreprocessor <arg> Pass <arg> on to the preprocessor -Xlinker <arg> Pass <arg> on to the linker -save-temps Do not delete intermediate files -pipe Use pipes rather than intermediate files -time Time the execution of each subprocess -specs=<file> Override built-in specs with the contents of <file> -std=<standard> Assume that the input sources are for <standard> -B <directory> Add <directory> to the compiler's search paths -b <machine> Run gcc for target <machine>, if installed -V <version> Run gcc version number <version>, if installed -v Display the programs invoked by the compiler -### Like -v but options quoted and commands not executed -E Preprocess only; do not compile, assemble or link -S Compile only; do not assemble or link -c Compile and assemble, but do not link -o <file> Place the output into <file> -x <language> Specify the language of the following input files Permissible languages include: c c++ assembler none 'none' means revert to the default behavior of guessing the language based on the file's extension Options starting with -g, -f, -m, -O, -W, or --param are automatically passed on to the various sub-processes invoked by gcc. In order to pass other options on to these processes the -W<letter> options must be used. For bug reporting instructions, please see: <URL:http://gcc.gnu.org/bugs.html>. prprt@flanker$ cat --help cat: illegal option -- - usage: cat [-benstuv] [file ...] prprt@flanker$ javac --help Unrecognized option: --help Could not create the Java virtual machine. prprt@flanker$
良く見ると、 一方、POSIX が気に入らない Sun が作った Java は 所で、使い方説明の中で <xxx> という項目が頻繁に出て来ます。これは、その文字列をいれるのではなく、ファイル名とかユーザー名、行番号、URL とかをいれろという意味です。要するに変数みたいなものです。<、 > で囲むのは、これらがシェルのリダイレク記号なので、これをコマンドオプションには絶対に出来ないから、混同する心配が無いからです。 一方、[xxx] となっているものが有ります。これは、それが省略可能だという意味です。正規表現とかBNFとか知っていますよね? 自分でプログラムを作るときには、この文化を守るようにしましょう。また、GNU では UNIX や Windows などの VAX/VMS の流れを引く OS には環境変数という仕組みが有ります。 環境変数は、プロセス毎に1セット持っています。在処は実用的プログラミングの基礎知識/メモリ管理の図1の だれが設定するのでしょうか? 置き場所を確保してくれるのは OS ですが、OS は値を設定してくれるわけでは有りません。これを理解するには、UNIX のプロセス/プログラムの起動の概念を理解する必要が有ります。それは、別の項目で説明するとして、ここでは、設定するための方法をお教えします。 シェルの環境変数を設定する(
sh , bash )mobius1@eagle$ export LANG=ja_JP.eucJP シェルの環境変数を設定する(
csh , tcsh )mobius1@eagle% setenv LANG ja_JP.SJIS シェルの環境変数は変えずに、違う環境変数を与えてプログラムを起動する(
env コマンドを使う) mobius1@eagle$ env LANG ja_JP.UTF-8 firefox 環境変数の値を表示する方法は mobius1@eagle$ printenv LANG ja_JP.eucJP あるいは、 mobius1@eagle$ echo ${LANG} ja_JP.eucJP 後者は環境変数をシェル言語の中で使う(参照する)やり方を示しています。つまり環境変数名を 環境変数が 名前1=値1\0.....\0名前N=値N\0\0 という形で、ヌルターミネートでビッチリ連結されて格納されいて、最後はヌルヌルターミネートです。 計算機上で日本語を使う際に常に悩ませ続けられるのが文字コードです。 Web ブラウザや高機能なエディタは自前で文字コードに対する処理をしてくれますし、テキストを読み込む際に文字コードの認識を自動的にやってくれます。 全く日本語を扱えないプログラムはしょうがないとして、一応対応しているプログラムに対しては、利用者がどの文字コードを使っているかを指示してやらなければなりません。 その為に
ルートウィンドウ(背景、実はここもウィンドウです)上で左クリックで、アプリケーション起動メニューが現れます。 ルートウィンドウ上での中クリック(ボタンが二つのマウスの場合は右左同時押し)で、ウィンドウ操作メニューが現れます。メニューを選択すると、ウィンドウ操作モードに入りマウスカーソルの形が変わます。このモードで操作対象のウィンドウを左クリックすると、選択した操作が実行されます(オブジェクト指向ではありません)。右クリックをするとウィンドウ操作モードはキャンセルされます。 ウィンドウのボーダーをドラッグするとウィンドウのサイズ変更ができます。Windows ユーザーが戸惑うのは、かならず、一旦、広げる方向に動かす必要があるということです。 ウィンドウのタイトルバーをドラッグするとウィンドウの移動。これは普通。 マウスがあるウィンドウのボーダーは赤くなりフォーカスされていることを示します。キーボードの入力はフォーカスされているウィンドウに入ります。Windows と違うのは、クリックしなくてもマウスを移動するだけで、フォーカスされるウィンドウが変わると言うことです。また、フォーカスされているウィンドウが必ず最上位に上がって来るわけではないというのも、Windows と違います。 ウィンドウ(の重なり)を最上位にするには、ウィンドウのタイトルバーかボーダーを左クリックします。 タイトルバーの左端はアイコン化ボタンです。 タイトルバーの右端はリサイズアンカーです。twm ではここ以外ではウィンドウのリサイズができません(その分、ウィンドウボーダーが無い)。ctwm はウィンドウボーダーを付けたので、どこでもリサイズができます。 仮想デスクトップの選択パネルがあります。クリックすると仮想デスクトップを切替えられます。狭いモニターでも同時に沢山の仕事がしやすくなります。自分なりに使い方を考えてください。 新しいウィンドウが現われるとき(アプリケーションを起動したり、ダイアログを出したり)、ウィンドウの位置を聞いて来ます(ワイヤー表現)。左クリックで確定します。右クリックすると、その位置から下側に最大化します。 これだけ覚えればとりあえずは作業はできます。ここから先は、 X Window は GUI システムとしてはは歴史が古く、また、クリップボードの使い方がオリジナルのまま残っているため、人気の新参者の Macintosh 系(Windows も GNOME も真似をした、Modifier-X/C/V のキーを使う操作)と全く違います。ただし、最近のアプリケーション (GNOME や KDE) では Mac 系の操作も併用できるようになってきています。 コピー: 選択(ハイライト)しただけでコピーされます。クリップボードは一つしかありませんから、複数のウィンドウを操作しているときに一つのウィンドウで選択をすると他のウィンドウのハイライトは解除されます。ただし、たまにこのハイライト解除が効かずに複数のウィンドウにハイライトが残ってしまう場合があります。そういう場合は、改めて選択した方が無難です。 ペースト: 中クリック(2ボタンのマウスでは左右同時押し、ホイールマウスではホイールがボタンになっています) カット: ありません cygwin/X のクリップボードは Windows のクリップボードと互換性があります。Windows 上でコピーして、cygwin/X に切替えてペーストしたり、その逆もできます。 注意:X Window のクリップボードはテキストしかサポートしていません。画像や書式付きテキスト等の複雑なデータの扱いはアプリケーション毎に違うため、通常はアプリケーション間でのクリップボード操作はできません。ただし、GNOME や KDE 等のデスクトップ環境では X Window のクリップボードに加えて自前のクリップボードシステムを持っているので、、異なったアプリケーション間でのクリップボードを実現しています。 日本語の入力の仕方は、カナ漢字変換エンジンやアプリケーションの種類によってまちまちです。Free Choice の UNIX では仕方の無いことです。 演習機ではカナ漢字変換エンジンとして Canna (http://canna.sourceforge.jp/)を用いています。 演習機で日本語入力ができるアプリケーションは Emcws と GEdit です。それぞれ使い方が違います。 Emcws での日本語入力日本語入力の開始/終了は Control-o です。 GEdit での日本語入力GEdit を含む GNOME 系アプリケーションの日本語入力は、日本語入力ライブラリ im-ja (http://im-ja.sourceforge.net/)によって実現されています。 日本語入力の開始/終了は、Shift-Space です。 画面のスナップショットの取り方ですが、方法は二つあります。一つは、cygwin/X だからできる方法です。もう一つは X Window の正しいやり方です。 cygwin/X は実はフルスクリーンモードで走っているにしても、所詮は Windows のアプリケーションだという事です。つまり、[Print Screen] キーを押せば、画面(全画面)がクリップボードに記録(コピー)されます。あとは、それを画像が扱える適当なソフト(ペイントでいいと思いますが)にペーストすればいいのです。 X Window の正しいやり方と言うのは、X Window に標準としてバンドルされている xwd というコマンドつかう方法です。これは、man ページで調べてください...ではちょっと難しいかな? mobius1@foxhound$ xwd -root -silent -out screen.xwd mobius1@foxhound$ ls -l screen.* total 7520 -rw-r----- 1 mobius1 mobius1 7683179 Oct 19 09:27 screen.xwd mobius1@foxhound$ convert screen.xwd screen.png mobius1@foxhound$ ls -l screen.* total 8736 -rw-r----- 1 mobius1 mobius1 1217368 Oct 19 09:30 screen.png -rw-r----- 1 mobius1 mobius1 7683179 Oct 19 09:27 screen.xwd mobius1@foxhound$
FreeBSD のマスコットきゃらのデーモン君(悪魔ではなく精霊です)が持っている三叉の槍。これは UNIX のプロセスの動きを表したものなのです。 実は、UNIX のシステムコールには、プログラムを起動する、というのは無いんです。有るのは、
#include <stdio.h> #include <unistd.h> #include <sys/wait.h> int main(int argc, char** argv) { pid_t ppid = getpid(); printf("I am pid = %d. Let\'s go !\n", ppid); if (fork() == 0) { printf("I am pid = %d, child of %d\n", getpid(), ppid); return 2; } else { int result; pid_t cpid = wait(&result); printf("I am pid = %d. My child (%d) exits %d\n", ppid, cpid, WEXITSTATUS(result)); return 1; } } 実行すると、 I am pid = 80386. Let's go ! I am pid = 80387, child of 80386 I am pid = 80386. My child (80387) exits 2 とプリントされます。二つに分かれる様が食器のフォーク(あるいは、三叉の槍。ただ、こいつらは二つ以上に分かれていますが...。フォークの名前を選んだ理由は実はもう一つ有って、ダイクストラがマルチタスクの説明に用いた「食事をする哲学者」で哲学者がスパゲティを食べるためにフォークを使うところからです。)の形に似ているからです。メモリの中身(コード、ヒープ、スタック)は勿論、ファイルディスクリプタや環境変数、そして CPU のレジスタ(一部はどうしても違ってしまいますが。例えばプログラムカウンタとかセグメントレジスタとか)までコピーされます。 一方、 そして、 このシンプルな仕組みの絶妙な組合せによって、UNIX は非常に多様な動きを実現することが出来るのです。とくに顕著なのは | (パイプ) です。 いわゆる「プログラムの起動」というのは、シェルとかウィンドウマネージャが自らを シェルもどき(このままではちゃんとは動きません)
int main(int argc, char** argv) { char* prog = argv[1]; // 最初のコマンドライン引数をプログラムファイルとして実行します。 char* back = argv[2]; // バックグラウンドに送るかどうか。'&' でチェック if (fork() == 0) { // ここは子プロセス exec(prog); // 子プロセスの中身は別のプログラムファイルによって上書きされ別人に... // でも、ファイルディスクリプタや環境変数という資産は引き継いでいる。 // でも、子プロセスは自分の親プロセス(のID)を知る術を持っていない... // ...どういう親子じゃ... } else { // ここは親プロセス(子を持ったから親になった!) if (*back == '&') { // バックグラウンドに送るということは、子プロセスの終了を待たないということ。 } else { // バックグラウンドに送らない、ということは、子プロセスの終了を待つと言うこと。 int result; pid_t cpid = wait(&result); // 子プロセスの終了を待つシステムコール } } } ※ アセンブラ(機械語)やCPUの動作(特に割り込み)について知識のあるひとは、マルチプロセスがどうやって実現されるか、自分で考えてみてください。面白いですよ。教官は OS は書いたこと無いですが、マルチタスクの仕組みをアセンブラで作ったことが有ります(プログラミング経験5年目)。そう、実はマルチタスクの仕組みを作ることは自体はそんなに難しいことじゃないです。それよりは、マルチタスクを活用したアプリケーションを作る方がずっと難しいです。 「ファイルを開く」というのはどういうことでしょう?通常はファイルの内容をメモリに読み込むことですね。メモリに読み込んだ後は、プログラムによってやることがちがいますが、ファイルの内容をメモリに転送する、という動きは共通です。共通の動きを何度も作るのは面倒ですので、誰かが良く考えられた使いやすい仕組みを作っておいてくれると助かります。OS は全てのプログラムの動きを支える基盤ですので、OS がそのような共通の仕組みを持つべきです。実際、どんな OS もファイルの内容をメモリに転送する仕組み(当然、その逆も)を持っています。歴史が UNIX より古い汎用機は独自の仕組みをもっていますが、UNIX 以降に登場した OS はみんな UNIX の仕組み(実際には Multics が下敷になっているのですが)に習っています。その仕組みとは... UNIX はファイルの管理とファイルの中身へのアクセスを全く別の仕組みで処理するようにしました。ファイル(ディレクトリも特殊なファイルです)の管理はファイルの名前で管理します。一方、ファイルの中身にアクセスする際にはファイル・ディスクリプタというデータを OS 内部のメモリに持つようにしました。なぜそうしたのか、それは、ファイル以外のもの(シリアル接続(モデムとか)、パラレル接続(プリンタとか)、ネットワーク)も同じように扱えるようにしたかったからです。 ファイル・ディスクリプタというデータは OS 直轄のメモリの中にあります。メモリの中に有るわけですから、OS が走っていない(まぁ、普通は電源が入っていない状況ということになりますが)時には存在しません。一方、ファイルはディスクの上に有りますから電源のオンオフは関係ないですね。OS 直轄のメモリの中は、プログラムからは絶対に触れません。そこで ファイル・ディスクリプタ を操作するためのシステムコールが用意されました。重要なものとしては、
さて、プログラムは ファイル・ディスクリプタ に直接はアクセス(ファイル・ディスクリプタ構造体へのポインタを入手してメンバー変数等にアクセス)することは出来ないです。そこで、 cat もどき#include <fcntl.h> #include <unistd.h> int main(int argc, char** argv) { for (int i = 1; i < argc; i++) { // 引数全部を処理します char buf[4096]; int fd = open(argv[i], O_RDONLY); // 引数をファイル名としてファイルを開きます。 ssize_t count; while ((count = read(fd, buf, sizeof(buf))) > 0) { // 読み込み write(1, buf, count); } close(fd); } } ここで、 実は、プロセスが起動されると、3つの ファイル・ディスクリプタ が既にできています(実際には、最初のプロセスのために OS が作ってあげて、後は fork()/exec() でそのままコピー/継承しているだけですが)。この三つが、標準入力(0)、標準出力(1)、標準エラー(2)です。 ちなみに、ファイル・ディスクリプタ の番号は使い回しされます。つまり、 UNIX のシェルの上では頻繁に "|" (パイプ)を使います。というか、パイプを使わないなら、UNIX を使う意義は全く有りません。パイプは 動くけど、あんまり意味のない
pipe() の使用例#include <unistd.h> int main(int argc, char** argv) { int fds[2]; pipe(fds); // パイプを作る write(fds[1], "hello, world\n", 13); // パイプに書き込む char buf[80]; int count = read(fds[0], buf, sizeof(buf)); // パイプから読み出す write(1, buf, count); // 結果を標準出力に出す close(fds[0]); close(fds[1]); }
止まりやすい
pipe() の使用例#include <unistd.h> int main(int argc, char** argv) { int fds[2]; pipe(fds); char buf[80]; int count; while ((count = read(0, buf, sizeof(buf))) > 0) { // 標準入力(0) から読み込み、 write(fds[1], buf, count); // どんどんパイプに流し込む } while ((count = read(fds[0], buf, sizeof(buf))) > 0) { // どんどんパイプから読みだし write(1, buf, count); // 標準出力(1)に書き出す。 } close(fds[0]); close(fds[1]); } このプログラムに(リダイレクトを使って標準入力から)大量のデータを流し込むと、途中で止まってしまいます。これは、 反対に小量を流し込んだ場合は、流し込んだ分を出力した後、止まってしまいます。これは、 この例から分かるように、一つのプロセスの中でパイプを使っても意味がなく、パイプに書き込むプロセスとパイプから読み出すプロセスの両方が同時に走っていないと、パイプの中をデータが流れないということです。 UNIX のシェルでは、コマンドの後に "&" をつけると、このコマンドをバックグラウンドで実行する指定になります(マルチプロセスのセクションを参照のこと)。実は、"|" は同じようにバックグラウンドで実行することを指定する記号なのです。"&" と違うのは、前のプロセスの標準出力と後のプロセスの標準入力をパイプを使って接続するのです。この仕組みはかなりトリッキーです。そのトリックの鍵は、 シェルが二つのコマンドをパイプで繋いで起動すると、プロセスは三つに分かれます。一つはシェルそのもの、残りの二つはシェルが起動したコマンド。プロセスが三つの分かれる様がデーモン君の持っている三又の槍なわけです。 |
|||||
Copyright © 2005 Takaya Sakusabe All rights reserved.
Generated: Apr 17, 2006 5:38:40 PM UTC. |