UNIXやLinuxのシステム管理(システム・コマンド・設定)に関する世界観2(コンパイラ)です。
自分の書いたブログ「神々とともに生きる詩人」2021/01/27より。
Linuxでは、主にC言語を用いてプログラムを記述し、それをGCCなどのコンパイラを用いてコンパイルする。
GCCでは、コンパイラの前段階としてプリプロセス(#defineや#includeなどの処理)を行い、コンパイル可能なC言語の完全なソースコードを生成したら、それをアセンブリ言語にコンパイルする。
コンパイラの手順としては、字句解析でトークンを解析し、構文解析で構文ルールからパースツリーを作成し、三番地コードなどの中間言語を作成し、最適化を行ってアセンブリ言語を出力する。
GCCでは、フロントエンドとバックエンドに構造が分かれており、フロントエンドではFORTRANなどのC/C++以外の言語にも対応して解析でき、バックエンドではさまざまなCPUアーキテクチャのアセンブリ言語を吐き出せる。
コンパイルが終わったら、アセンブルして機械語のオブジェクトファイルを生成し、リンクを行う。
機械語のオブジェクトファイルになった段階で、プログラムは実行可能なプログラムとライブラリに分かれている。
ライブラリには二つの種類があり、スタティックリンク(*.a)では、リンクの時点で完全に実行プログラムと結合される。
一方、ダイナミックリンク(*.so)では、リンクの時点では結合を行わず、実行時にリンクローダ(/lib/ld-linux.so.2)が動的にリンクを行う。
最近のUNIXでは、ダイナミックリンクを使うのが一般的だが、一部の軽量libc(Muslなど)ではスタティックリンクに最適化されたものもある。
また、全てのプログラムは暗黙のうちに、libc.so.6にダイナミックリンクされている。
バイナリがどの共有ライブラリに依存しているかはlddコマンドで確認できる。
詳しくは以下の書籍が参考になります。
コンパイラとは、プログラミング言語で書かれたソースコードを機械語に翻訳するアプリケーション。一度のコンパイルで全てを機械語に変換する。
インタプリタとは、プログラミング言語を「動的に」読み込みながら、逐次機械語の命令に翻訳していくアプリケーション。
UNIXにおいては、プログラムには二種類ある。それは「プログラム」と「ライブラリ」である。
main()関数が含まれたプログラムには、実行命令が下ると即座にmain()関数から実行を始める。多くのコマンドプログラムや自立したアプリケーションはこちらに当たる。
これに対して、main()関数が含まれていないライブラリと呼ばれるプログラムは、それ単体では実行できない。
ライブラリは、別のプログラムから呼び出される形で実行される。
ライブラリは、さまざまなプログラムに点在する「呼び出し命令」に基づいて、プログラムの中で下請けの専門業者のように、必要なところで必要な時に実行される。
ライブラリやその呼び出し規約を「API」と呼ぶ。このAPIに基づいた命令によって、プログラムは「難しい機能でも簡単に呼び出せる」ようになり、たとえばCの標準APIが含まれるlibcや、X11のAPIが含まれるxlib、あるいはGTK+やQtのようなツールキットなど、さまざまな命令や機能を「自分のプログラムから呼び出したい」時に、こうしたAPIを使う。
ライブラリAPIを使うことで、全てのことを自分で作る必要はなく、ライブラリを呼び出す形で簡単に高度で複雑なプログラムを開発できる。
/binディレクトリには、実行可能ファイルを置き、/libディレクトリには共有ライブラリ(*.so)を置くのが一般的。
また、/usr/binや/usr/libがある環境では、/binや/libにはシステムを起動し管理するための最低限のプログラムを置き、アプリケーションプログラムなどは/usr/binや/usr/libに置く。これにより、もし/usrディレクトリが破壊されても、/binや/libが残っていればシステムの復旧や最低限の管理を行うことができる。
Linuxファイルシステム階層も参照のこと。
/etc/ld.so.confは、共有ライブラリがどこにあるかを指定するパスが含まれた設定ファイルで、ldconfigはこの情報をOSに伝えるコマンド。
共有ライブラリを参照のこと。
以下の書籍が参考になります。
C/C++など、多言語に対応したフリーのコンパイラ集。
GNUツールチェインも参照のこと。
デバッガ。
デバッグも参照のこと。
GCCは、様々な言語に対応しているが、フロントエンドでC、C++、Objective-C、Fortran、Ada、Goをコンパイル出来る。中間コードで最適化処理(コードをもっと高速にする改変)を加えて、バックエンドでさまざまなCPUアーキテクチャに対応する。
GCCもLLVM/Clangにとってかわられようとしている。
基本:
gcc -o hoge hoge.c
デバッグ情報を付加してhoge.oとlibm.aをリンクする場合は以下のようにする:
gcc -g -o hoge -lm hoge.o
詳しくは以下のページが参考になります。
GNU makeは、プログラムのソースコードをコンパイルする時、更新されたファイルだけをコンパイルするなど、色んなことが出来ます。
以下の書籍が参考になります。
Makeも参照のこと。
Autotoolsは、色んなプラットフォームでソフトウェアをコンパイルする時に自動で設定・処理してくれるGNUの仕組みですが、結構理解し辛いので嫌われています。
このコマンドを実行することで簡単にソフトウェアがインストール出来るのは、Autotoolsのおかげ:
./configure && make && make install
--prefix=dirでインストール先を変更できる。
Autotoolsも参照のこと。
GNUツールチェインの詳細はGNUツールチェインをご覧ください。
コンパイラの仕組みについてはコンパイラ開発も参照のこと。
僕は、UNIXのシステムを考える上で、「テキストのソースコード」と「機械語のバイナリ」が重要だと思います。
実際のところ、プログラミングを行う上でもシステムを管理する上でも、大切なのは「機械語のバイナリ」であり、同時に、バイナリから読み書きしたりバイナリの外部に保管したりするものは「テキストデータ」であり、バイナリのほとんどは「テキストデータが元にできている」ということです。
僕は、これを直観的に理解するために、Gentoo LinuxのPortageを使ってみることをおすすめします。全てのプログラムがテキストからバイナリへと変換されていく過程が分かりますし、設定は全てテキストファイルを手動で編集します。Gentoo Linuxを触ることで、「UNIXのどの部分がテキストで、どの部分がバイナリで、どのようにテキストからバイナリが形成されていくか」が、目で見て分かるのです。システムの全てのファイルを最初から最後まで全て形成していく過程が、Gentoo Linuxのインストールによってしかと見れます。またマウントやフォーマットなどのUNIXに必須のスキルも、Gentooのインストールと設定を行うことで、管理することができるようになります。
自分の書いたブログ「神々とともに生きる詩人」2021/01/14より。
Linuxについて言えるのは、コンパイラが重要ということ。
システム全体をビルドするコンパイラが、きちんと動かないと、何も動かない。
逆に、動かなくても最新のコンパイラを使うことで、バグ潰しになる。
カーネルと同様、コンパイラが重要である。