こんにちは。
プログラミングの勉強のために筑波大の講義「システムプログラミング」をやってみようと思います。
このエントリは完全な個人のメモです。
筑波大の講義ページはこちら。
お勉強のためにこの本を読んでいるので、内容を覚えるためにSummarizing(サマライジング)を行います。
正直、講義ページがすでにかなり簡潔にまとまっているのでほぼコピペになってしまう気もしています。
講義「システムプログラミング」の目的
OSや計算機システムの理解を深め活用できるようになるのと、プログラミングスキルの習得が目的。
OS(Operating System)
OSはハードウェアによる違いを隠蔽し、ユーザが操作しやすい、またはプログラムを実行しやすい環境を提供する。
OSのおかげでHDDやマウスなど、物理的なハードウェアの違いをユーザは意識することなく操作することができる。
API(Application Program Interface)を用いたプログラミング
ユーザがコンピュータを操作するための手段として、OSはAPIを提供している。
ユーザはこのAPIを使用することでハードウェアの操作や制御を行う。
APIは一般的に以下により提供される。
・OSカーネルが提供するシステムコール
・ライブラリやマクロ
APIを上手く使うと、生産性、信頼性、移植性の向上など様々なメリットがある。
演習環境
筑波大の講義ではMac OS Xを使用するが、このブログではUbuntuを使用する。
講義では主にPOSIX(Portable Pperating System Interface for Unix) APIを使用してプログラムを作成する。
ソフトウェアの構成とシステムプログラム
プログラムの種類
※筑波大の講義ページからそのまま拝借
UNIX OSの構成要素
※筑波大の講義ページからそのまま拝借
今回私が使用するUbuntuも基本構成は上と同じ。
以下、それぞれの構成要素の概要を説明する。
・シェル
UNIXでよく使われるユーザーインターフェイスがシェル(shell)。
一般的によく使われるshellには以下のようなものがある。
bsh(Bourne Shell), bash(Bourne-Again Shell), csh(C Shell), tcsh(Tenex-like C Shell)など。
shellはCLI(Command Line Interface)からユーザのコマンドを受け付け、そのコマンドを解釈、実行してその結果を出力する。
・XウインドウシステムとGUI(Graphical User Interface)
最近のOSでは、GUIも完全にOSの機能の一部として提供されているが、一般的なUNIX OSではXウインドウシステムとウインドウマネージャをいう独立したプログラムでGUIが提供される。
Xウインドウシステムは、ビットマップディスプレイ上にウインドウ
を表示するための基本的な機能を提供する。
GUIはGNOMEやKDEなどのプログラム群(デスクトップ環境)により提供される。
・コマンドとアプリケーション
コマンドとは、ユーザがシェルを通してコンピュータに与える命令のこと。
コマンドはシェルの組み込みコマンドの場合もあるし、プログラムである場合もある。
組み込みコマンドとは、cd, exit, historyなどシェルの状態を変更するコマンドやシェルが持つ情報を表示するコマンドなどを指す。
プログラムについては、コマンドやアプリケーションと呼ぶことがあるが、明確な基準はなく、Officeやゲームなど自己完結的なプログラムをアプリケーションと呼ぶことが多い。
・サーバとデーモン
デーモンとは、バックグラウンドで動作し様々なサービスを提供するプログラムのことを指す。
最近ではサーバと呼ぶことも多い。
例えば、メール配信機能を提供するプログラム、ネットワーク機能を提供するプログラムなど。
・システムコールとライブラリ、ミドルウェア
システムコールはOSカーネルの機能を直接呼び出すためのAPIのこと。
ライブラリとミドルウェアはプログラムの部品となる関数の集合。
ライブラリとミドルウェアの定義は曖昧だが、ライブラリは様々な用途のプログラムに大して機能を提供するものであるのに対し、ミドルウェアは特定のプログラム(通信やGUI)に対して機能を提供するものという見方がある。
システムコールもライブラリ、ミドルウェアも、Cプログラムから呼び出す場合は関数呼び出しの形式で使用することができる。
・UNIXカーネル
UNIXカーネルとは、プロセッサ特権モードというハードウェアのすべてを制御することができる動作モードで動き、ハードウェアを直接制御するプログラムのこと。
UNIX環境で特権モードで動作するプログラムはカーネルだけ。
その他のプログラムはユーザモードで動作し、ハードウェアへのアクセスは制限される。
UNIXカーネルはプロセス管理、ファイルシステム、メモリ管理、ネットワーク、デバイスドライバなどを含む大きなプログラムである。
プログラム、ライブラリ関数、システムコールの関係
ライブラリ関数とシステムコールはプログラマから見ると似たような概念だが、以下のような違いがある。
・実行時にライブラリ関数はプログラムの一部として(そのプログラムのプロセスの中で)動作するが、カーネルは独立したプログラムとして(別プロセスで)動作する。
・プロセスはシステムコールを通してのみ使用できる。
・ライブラリ関数を使用して入出力を行う場合、ライブラリ関数の中ではシステムコールが使用される。
・システムコールを使用しないライブラリ関数も存在する。
プログラムの実行環境
プログラムとプロセス
簡単にいうと、実行中のプログラムがプロセス。
同じプログラムを同時に複数動かすと、その分だけプロセスが生まれる。
プログラムはCPUが実行できる機械語命令と、それによって処理されるデータの集合のことを指す
プロセスは実行中のデータを持つ。
プログラムの実行は通常shellから行う。
プロセスを終了させるには、(そのプロセス自身も含む)誰かが終了のためのシステムコールを呼ぶか強制終了させる必要がある。
プロセスの観察
プロセスの情報を見るためのコマンドとして、ps, top, pstreeコマンドなどがある。
プログラムの開発環境
マニュアルの読み方
manコマンドを使用する。
マニュアルは以下のような構成になっている。
1章:コマンド
2章:システムコール
3章:ライブラリ関数
4章:デバイスファイル
5章:ファイル形式
6章:ゲーム
7章:その他
8章:管理用コマンド
manコマンドでは1章から検索をかけていき、最初にヒットしたものを表示する。
システムコールとライブラリ関数で同じ名前の関数があるときは気を付けなければならない。
たとえば
と書くとコマンドのマニュアルが表示されるが、システムコールのマニュアルを見たい場合は
とする必要がある。
プログラムの作成から実行までの簡単な流れ
1. エディタでプログラムを書く
2. コンパイルして実行ファイルを得る
3. 実行する
インデンテーション
筑波大講義では8文字字下げを使うようですが、字下げは個人の好みで行ってください。
私は恐らく一番メジャーな4字字下げにします。
括弧や空白の入れ方なども個人の好みでどうぞ。
プログラム全体で統一されていればなんでもいいと思います。
コンパイルとリンク
例として以下のプログラムをコンパイルする。
※1~10の総和を求めるプログラム。筑波大の講義ページからそのまま拝借
以下のコマンドでコンパイルできる。
実行ファイルa.outが得られるので実行すると以下のようになる。
実行コマンド
実行結果
ccコマンドにより起動されるプログラム
ccコマンド自体はコンパイルを行わず、コンパイルに必要なコマンドを呼び出すだけ。
-vオプションを付けるとccから呼び出されるプログラムがわかる。
clangやldといったプログラムが呼び出される。
cc -E
ccに-Eオプションを付けて実行するとプリプロセッサだけが起動され、結果が標準出力に出力される。
マクロがうまく展開されないときなどに便利。
リンク
ccに-cオプションを付けて実行すると、アセンブラまで実行され、機械語で書かれたsum.oというオブジェクトファイルが得られる。
このオブジェクトファイルを引数にccを実行すると、ccはサフィックス(.o)から引数がオブジェクトファイルであると判断してリンカを起動する。
リンカはオブジェクトファイルだけでは解決できなかったシンボル(変数名や関数名のこと)の参照解決を行う。
sum.oの場合はprintf関数が解決される。
動的リンクと静的リンク
リンクの方法には動的リンクと静的リンクの2種類がある。
動的リンクはプログラム実行時にプログラムとライブラリをリンクする。
静的リンクはリンク時にライブラリとリンクする。
動的リンクを使うと実行ファイルの外部にある関数を参照するため、プログラムサイズやメモリサイズを削減できるが、リンクを実行時に行うため実行速度は遅くなることがある。
動的リンクされたライブラリはlddコマンドで知ることができる。
makeコマンド
C言語のプログラムはファイル単位でシンボルのスコープを持つ。
また、大きなソースファイルのコンパイルには時間がかかる。
そのためプログラムは適度に分割して作成することが望ましい。
分割したファイルをそれぞれcc -cでオブジェクトファイルにし、最後にリンクを行うことでコンパイルの時間を短縮できる。
この手順を自動化するのがmakeコマンド。
makeコマンドはmakefileを読み込み、そこに書かれているルールに従ってコンパイルを行う。
makefileの書き方は以下。
makefileが複雑になると処理が追いづらくなるが、その時はmake -nを実行するとコマンドを処理の流れとともに表示する。
デバッガ
gdbの説明。
gdb ./a.outなどでデバッガモードでプログラムを起動できる。
ステップ実行や変数の書き換え、表示などが可能。
プログラミングとデバッグ
バブルソートで簡単なC言語の構文のおさらいとprintfデバッグの説明。
超基礎的な内容なので割愛。
ポインタ
ポインタとは
超簡単にいうと、アドレスのこと。
ライブラリやカーネルはアドレスを受け取り、そのアドレスに対してデータを読み書きする。
ポインタを使うと何が嬉しいか
参照渡しなので、例えば関数を呼ぶ側と呼ばれる側で同じ場所を参照/変更することができる。
ポインタを使うときの注意点
間違ったアドレスへの参照や変更を行うと重大なバグが起こる。
ポインタ変数を宣言する場所にも注意が必要。
グローバル変数やmalloc関数などで確保したヒープ領域ならプログラム全体で有効(ヒープ領域は開放されるまで)だが、ローカル変数は宣言した関数の中でのみ有効。
a関数内で宣言されたポインタを、a関数が終わった(return)後に参照するとこれも重大なバグにつながる。
まとめ
第1回の講義内容は以上です。
途中からかなり雑になってしまった感は否めませんが、正直私のメモ見るよりも講義ページが良いと思います。
非常に簡潔にまとまっていてわかりやすいです。
第2回の講義内容は以下。
おわり。