システムプログラミング 第6回

システムプログラミング
第6回、7回、8回
情報工学科 篠埜 功
今日からの内容
• Linuxのシステムコールを用いたCプログラム
の作成
• 今日の内容
– Cプログラムのmain関数の引数について
– Usage メッセージについて
– システムコールのエラーメッセージについて
Cのmain関数の引数について
/* コマンド名および引数を表示 */
#include<stdio.h>
int main (int argc, char *argv[ ]) {
for ( ; *argv; argv++)
printf ("%s\n", *argv);
return 0;
}
(実行例) $ gcc 1.c
$ ./a.out abc 234
./a.out
abc
234
$
main関数の第3引数
main関数は第3引数に環境変数の情報の配列が渡される場
合がある。(ISO規格で定められているわけではなく、処理系
依存。)
#include<stdio.h>
int main (int argc, char *argv[ ], char *envp[ ]) {
for ( ; *envp; envp++)
printf ("%s\n", *envp);
return 0;
}
芝浦工大の環境では第3引数を受け取れる。第3引数は
なくてもよい。環境変数はライブラリ関数getenv()で取得で
きるのでそれを使えばよい。
スタートアップルーチン
すべてのC言語プログラムは、実行形式ファイルにすると
きに、スタートアップルーチンとリンクされる。
スタートアップルーチンは、/usr/lib/crt1.oにある。
$ nm /usr/lib/crt1.o
で確認。この出力結果に
U main
という行が含まれており、スタートアップルーチンがmain
関数を呼び出していることがここに反映されている。
スタートアップルーチン部分で引数の個数、引数の文字
列配列、環境変数配列をmain関数に渡す。
コマンドのオプションについて
• オプションはハイフンのあとに1文字(-oなど)
• オプションのあとにオプションの引数があること
もある
– (例) gcc –o main main.c など。
• いくつかのオプションをまとめて記述することも
ある
– 例 ls –la は、ls –l –a をまとめて書いたものである。
• --helpのように、ハイフンが2つの場合もある。
(これは-h –e –l –pを-helpと書いた場合との区
別のため)
Usage メッセージ
(1) オプションが正しく与えられなかった場合
(2) コマンドの引数に過不足があった場合
このような場合にメッセージを出すのが普通。こ
れをUsageメッセージという。
例えば、 $ cp のようにcpコマンドを引数無しで
実行すると、Usageメッセージが表示される。
例(打ち込んで確認)
/* Usageメッセージを表示するだけのプログラム */
#include<stdio.h>
int main (int argc, char * argv[]) {
if (argc!=2)
fprintf (stderr, "Usage: %s filename\n", argv[0]);
return 0;
}
(実行例)
$ gcc usage.c
$ ./a.out
Usage: ./a.out filename
システムコールについて
• システムコールとは、カーネル内のコードを呼び
出すためのC関数
• ファイル、ネットワーク、キーボード等、ハード
ウェアとのやりとりはすべてカーネルが行う。
ユーザはシステムコールを通じてカーネルに
ハードウェア操作を依頼する。
• 例えば、ファイルからデータを読み出す場合、
getc, fgetcなどのライブラリ関数を呼ぶが、どん
なライブラリ関数を呼んだとしても、最終的には
readシステムコールが呼ばれ、カーネル内部の
コードが実行される。
システムコールの実装
システムコールは、Cの関数であり、ライブラリファ
イル /usr/lib/libc.a に入っている。
システムコールであるCの関数の中で、ハードウェ
ア割り込みを起こす命令(トラップ命令)が実行さ
れる。これにより、カーネルの割り込み処理部分
に制御が移る。トラップ命令実行時にシステム
コールの番号を伝え、それによってそれぞれのシ
ステムコールに対応するカーネルのコードに制御
が移ることになる。
$ ar t /usr/lib/libc.a | less
でlibc.aの中身のオブジェクトファイルリストが表示される。
write.o, read.oなどが入っている。
システムコールの番号
1 exit
2 fork
3 read
4 write
5 open
6 close
7 wait
8 creat
9 link
10 unlink
11 exec
12 chdir
…
今後の講義でシステムコールを少し
ずつ紹介する。各システムコールのC
関数が番号の設定を行うので番号は
知らなくてよい。
システムコールのマニュアル
システムコールはmanコマンドでマニュアルを表示できる。
例えば、
$ man -S 2 write
でwriteシステムコールのマニュアルが表示される。
$ man write
とすると、writeコマンドのマニュアルが表示される。
$ man –a write
とすると、writeの名前のマニュアルがすべて(PAGERがlessの
場合はqを押すごとに)順番に表示される。
manコマンドの-Sの引数には分類番号を入れる。1はコマンド、
2はシステムコール、3はライブラリ関数となっている。
システムコールのエラーメッセージ
• システムコールがエラーになった場合、エラー番
号がerrnoという外部変数に代入されている。こ
の番号を用いてエラーメッセージを出力するライ
ブラリ関数perrorがあるのでそれを用いる。
(errnoを直接使うプログラムは書かないほうがよ
い。)
• 例えば、$ ls aaa のように、lsコマンドで存在しな
いファイル名を指定した場合に、そのようなファ
イルは存在しないといったメッセージが出力され
る。ここではperror関数が用いられている。(その
ようにlsコマンドが実装されている。)
ライブラリ関数perror
void perror (char *s)
ライブラリ関数perrorは文字列(charへのポイ
ンタ)を引数として受け取り、それを出力し、コ
ロンと空白を出力したあとにシステムコールの
エラーメッセージを表示する。
ファイルの1文字目を表示する例
(入力して実行、後で詳述)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define ERR -1
int main (void) {
int fd, n;
char c;
if ((fd = open ("test",
O_RDONLY))
== ERR) {
perror ("open");
exit(1);
}
/* 続き */
if ( (n = read (fd, &c, 1) ) > 0 )
printf ("1文字目は%cです。\n",
c);
if (n==ERR) {
perror ("read");
exit(1);
}
if (close (fd) == ERR) {
perror ("close");
exit(1);
}
return 0;
}
演習課題
さきほどのプログラムを参考にして、テキストファイルの2文字
目まで表示するプログラムをopenシステムコール, readシステ
ムコールを使って作成せよ。
(さきほどと同様、printfは使うことにする。)
ファイル、プロセス
• OS(Linux)は、ファイルとプロセスを木構造で
管理する。
• まず、ファイルの木構造について学習する。
ファイル操作のシステムコール
ファイルはOS(Linux)が管理している。
ファイルの操作等を行うためのシステムコー
ルが提供される。
open, creat, close, read, write, lseek, dup,
dup2等がある。
今日はopen, close, read, writeの解説を
行う。この前に、まず、ファイルの基本事
項の説明を行う。
ファイルとは
UNIX系OSではファイルは1つの木構造で管理され
る。ルートディレクトリ以外は、すべてのファイルに
は親(ディレクトリ)がある。
ディレクトリもファイルの一種であり、そのディレクト
リの子供のファイルの名前を保持しているファイル
である。ディレクトリファイルも、(ルートディレクトリ
以外は)何らかのディレクトリの子供である。
すべてのファイルはルートディレクトリから親子関
係を辿ることによって到達できる。
デバイスファイル
UNIXでは、端末、ディスク、磁気テープ、プリンタなど
の周辺機器やメモリなどの装置のそれぞれに対応す
るファイルがある。特殊ファイル、あるいはスペシャ
ルファイルとも呼ばれる。これらのファイルは通常
/devディレクトリ以下にある。
デバイスは次の2種類に分けられる。
• キャラクタデバイス --- 端末やプリンタなど、文字単
位で入出力を行う装置
• ブロックデバイス --- ディスクや磁気テープ装置な
ど、データをある程度まとまったブロック単位で処理
する装置
デバイスファイルも普通のファイルと同様に扱える
(UNIXの利点)
ファイル名、パス名
• ファイルには、名前がある。それをファイル名と
いう。ディレクトリが違えば同じファイル名であっ
ても別のファイルである。
• パス名は、経路(パス)をファイル名の前につけ
たものである。
• ファイル名は255文字以内、パス名は4095文字
以内。大文字、小文字、数字、ピリオド、ハイフン、
アンダーバーが使える。ファイル名に日本語は
使えるが、現状では使わないのが無難。
絶対パス、相対パス
• 絶対パス --- ルートディレクトリから下向きに
辿るパス
• 相対パス --- 現在のディレクトリからの相対的
な経路で表すパス名、上に行くこともある。
• ピリオド --- 現在のディレクトリ(current
directory)を表す。
• ピリオド2つ --- 親ディレクトリを表す。
(例) ../.. は2つ上の親のディレクトリを表す。
ピリオドの役割
ピリオド . はカレントディレクトリを表す。カレントディレクトリの
実行形式ファイルを実行するとき、
$ ./test
のようにして実行する。単にファイル名を
$ test
のように打った場合は、testという実行形式ファイルを、シェル
(例えばtcsh)が、シェル変数PATHに格納されているディレクトリ
を順番に探すことになる。PATHに . が指定されていない場合は、
カレントディレクトリにtestがあっても実行できない。また、testと
いう名前の実行ファイルがシェルの内部コマンドだったり(実際
は違うが)、他のPATHの(順番が先になっている)ディレクトリに
あると、そちらが実行される。(実際に、/usr/bin/testが存在す
る。)
ホームディレクトリ
ログイン時のディレクトリをホームディレクトリと
いう。通常、/homeの下に作成される。(rootとい
うユーザ(スーパーユーザ)のホームディレクトリ
は/rootである)。演習室では、(私sasanoの場
合)/home/sitの下にある。ディレクトリを変更す
るコマンドがcd(シェルの内部コマンド)であり、
これはchdirシステムコールを使って実現されて
いる。ホームディレクトリはチルダ~で表される。
ファイルの保護
ファイルを他人(他のユーザ)に見られないように、
permissionの指定ができる。
$ ls –al
で確認できる。
d rwx rwx rwx
の最初の桁以外は変更できる。
最初がdだとディレクトリ、-は一般のファイル。
そのあとは、owner, group, otherに対する読み書
き実行の許可を表す。
(例) -rw-r—r--だと、一般ファイルで、所有者は
読み書き、グループメンバーとその他のユーザは
読み出しのみ許可。
Permissionの変更
Permissionの変更はchmodコマンドで行う。
$ chmod モード ファイル名
モードは、rwxの組を3桁の2進数と考え、これを
8進1桁で表わし、8進3桁で指定することができ
る。例えば、rw-r--r--にしたい場合は644とする。
chmodは、chmodシステムコールを使って実現
されている。
ファイルの所有者
ファイルには所有者、グループが関連づけられてい
る。
$ ls –l
で確認できる。
ファイルの所有者の変更はchownコマンド、グルー
プの変更はchgrpコマンドで行う。これらはchownシ
ステムコールを使って実現されている。ただし、所有
者とスーパーユーザ以外は変更できない。
ディレクトリファイル
ディレクトリファイルには、ファイル名からiノード
番号(iノードはindex nodeの略)への対応がファ
イルの数だけ格納されている。(実際のデータ
構造は、ハッシュテーブルや線形リストなど。)
ファイルのiノード番号は
$ ls –li
等、lsに-iオプションを与えると確認できる。
ファイルシステムの実装
ファイルシステムは、ブートブロック、スーパーブロック、
iノードリスト、データブロックの4つの領域から構成され
る。
• ブートブロック --- OSを起動するためのプログラムを
格納。
• スーパーブロック --- ファイルシステムの大きさ、ブ
ロック数、ブロックサイズ、空きブロック、iノードリストの
大きさ、空きiノードリストの情報などを格納。
• iノードリスト --- iノードのリスト。iノードには各ファイル
の種類、所有者、permission、変更時刻、ファイルサイ
ズ、データブロック内の場所などが格納されている。
• データブロック --- ファイルの中身が格納されている。
openシステムコール
ファイルからのデータの読み込み、ファイルへのデー
タの書き込みをするには、まずファイルをオープンす
る。これを行うのがopenシステムコールである。
openシステムコールを使う場合、types.h, stat.h,
fcntl.hをインクルードする。
openシステムコールの引数
openシステムコールは、パス名、フラグの2引数ある
いは、これらにモードを加えた3引数で呼び出す。
パス名で指定されたファイルを、フラグに従ってオープ
ンし、ファイル記述子(file descriptor, int型)を返す。フ
ラグがO_CREATの場合、モードが必要。
ファイル記述子は、利用者ファイル記述子表の
何番目かを表す。
オープンするとは、データ入出力用のバッファを確保し、
利用者ファイル記述子表中の1つの構造体を割り当て、
構造体の各メンバーに初期値を設定することをいう。
利用者ファイル記述子表についてはdupシステムコー
ルの説明時に説明する。
openシステムコールの代表的なフラグ
O_RDONLY --- 読みだしのみ
O_WRONLY --- 書き込みのみ
O_RDWR --- 読み出し、書き込みの両方を行う
O_CREAT --- ファイルが存在しない場合作成する。第3引
数のモードでファイルのpermission等を設定する。
これらのフラグはfcntl.hに記述されているので、インク
ルードして使う。
モード
openシステムコールの第3引数に与えられるモー
ドでは、ファイルのpermissionおよびセットユーザID
ビット、セットグループIDビット、stickyビット(/tmpな
どで使用)を12桁の2進数で表す。代表的な数値
はstat.hでマクロとして提供されているが、数値で
直接指定してよい。
(例)S_IRWXU --- 所有者はread, write, executeが
できる。
その他にもあるが、
$ man –S 2 open
で確認。
readシステムコール
ファイルからデータを読み出すためのシステムコー
ルがreadシステムコールである。
unistd.hをインクルードする。
ファイル記述子、データ格納領域へのポインタ、読み
出しバイト数を引数に与える。返り値は読み出したバ
イト数。これが0のときはファイルの最後まで読み終
わっているということになる。
readシステムコールが正常終了しなかった場合は-1
が返ってくる。このときはperrorでエラーメッセージを
表示する。
writeシステムコール
ファイルへデータを書き込むためのシステムコール
がwriteシステムコールである。
unistd.hをインクルードする。
ファイル記述子、書き込むデータが格納されている
領域へのポインタ、書き込みバイト数を引数に与える。
返り値は書きこんだバイト数。
writeシステムコールが正常終了しなかった場合は-1
が返ってくる。このときはperrorでエラーメッセージを
表示する。
closeシステムコール
closeシステムは、ファイル記述子を引数に受け取
り、そのファイルをクローズする。
クローズするとは、入出力用バッファを解放し、利
用者記述子表内の構造体を解放することをいう。
(同時に開けるファイル数に制限があるので、閉じ
るのがよい。閉じなければプロセス終了時に閉じ
られる。)
例1:テキストファイルの先頭にaを書き込む
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main (void) {
int fd, n;
char c = 'a';
if ((fd = open ("test", O_WRONLY))
== -1) {
perror ("open");
exit(1);
}
/* 続き */
if (write (fd, &c, 1) != 1){
perror ("write");
exit(1);
}
if (close (fd) == -1) {
perror ("close");
exit(1);
}
return 0;
}
演習課題2
• テキストファイル(ファイル名はtestなど)の先
頭文字を読み取り、その文字を2文字目に書
きこむ。Openシステムコールの第2引数(フラ
グ)はO_RDWRにする。
read, writeシステムコールを呼ぶたびに、読
み書きのためのポインタ(システムコール内部
のポインタ)が1つ進むので、readで読み取っ
たあとにwriteで書き込めば、2文字目に書き
こまれることになる。
例2
/* テキストファイル全部表示 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main (int argc, char * argv []) {
int fd, n;
char c;
if (argc!=2) {
fprintf (stderr,
"Usage: %s filename\n",
argv[0]);
exit(1);
}
/* 続き */
if ((fd = open (argv[1], O_RDONLY))
== -1) {
perror ("open");
exit(1);
}
while ((n = read (fd, &c, 1) ) > 0)
printf ("%c", c);
if (n==-1) {
perror ("read");
exit(1);
}
if (close (fd) == -1) {
perror ("close");
exit(1);
}
return 0;
}
複数バイトずつ読み込む
readシステムコールで、複数バイト単位で読み込む
こともできる。
readシステムコールの返り値は、
(1) 正の場合、読み込んだバイト数を表す。
(2) 0 の場合、ファイルの内容を既に全部読み終
わっていたことを表す。
(3) -1の場合、システムコールが何らかの理由で正
常終了しなかったことを表す。この場合はライブラリ
関数perrorでエラー内容を表示するべき。
例えば、ファイルサイズ260バイトのファイルを100バ
イト単位で読み込むと、最後の回は60バイト(返り値
も60)になり、その後は返り値は0となる。
例3
/* テキストファイル全部表示 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main (int argc, char * argv []) {
int fd, n;
char c[100];
if (argc!=2) {
fprintf (stderr,
"Usage: %s filename\n",
argv[0]);
exit(1);
}
/* 続き */
if ((fd = open (argv[1], O_RDONLY))
== -1) {
perror ("open");
exit(1); }
while ((n = read (fd, c, 100) ) > 0)
if (write (1, c, n) != n) {
perror ("write"); 1は標準出力
exit(1); };
if (n == -1) {
perror ("read");
exit(1); }
if (close (fd) == -1) {
perror ("close");
exit(1); }
return 0;
}
例4
/* 標準入力を標準出力へ書きだ
す*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main (int argc, char * argv []) {
int fd, n;
char c[100];
while ((n = read (0, c, 100) ) > 0)
if (write (1, c, n) != n) {
perror ("write");
exit(1);
};
/* 続き */
if (n == -1) {
perror ("read");
exit(1);
}
return 0;
}
0は標準入力
1は標準出力
新しいファイルの作成
新しいファイルの作成は、openシステムコールの
第2引数のフラグにO_CREATを指定する。(ファイ
ルが存在していたらそのファイルを使う。存在しな
ければファイルを新しく作る。)
他のフラグと組み合わせて指定できる。組み合わ
せるときはビットのor演算子|を用いる。
例えば、write onlyで開きたい場合は、openシス
テムコールの第2引数に
O_WRONLY | O_CREAT
を指定する。さらに、存在しているときに内容を消
したいときは O_TRUNCをさらに追加で指定する。
例5
/* 入力をファイルへ書きだす*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main (int argc, char * argv []) {
int fd, n;
char c[100];
if (argc!=2) {
fprintf (stderr,
"Usage: %s filename\n",
argv[0]);
exit(1);
}
/* 続き */
if ((fd = open (argv[1],
O_WRONLY | O_CREAT |
O_TRUNC, 0644)) == -1) {
perror ("open");
0は標準入力
exit(1); }
while ((n = read (0, c, 100) ) > 0)
if (write (fd, c, n) != n) {
perror ("write");
exit(1); };
if (n == -1) {
perror ("read");
exit(1); }
if (close (fd) == -1) {
perror ("close");
exit(1); }
return 0;
}
演習課題3
実行ファイルの引数に2つのファイル名を受けとり、
1つ目のファイル(存在するファイル)のコピーを2つ
目のファイルに作成するプログラムを作成せよ。(コ
ピーコマンド)
コピー先ファイルのopen時の引数
第2引数 --- O_WRONLY | O_CREAT | O_TRUNC
第3引数 --- 0644(8進表記)
とせよ。これにより、コピー先のファイル名が存在し
ていたら、内容が消去されてから書き込まれる。
レポート課題3
catコマンドの以下の機能を、システムコール(open,
close, read, write)を使って実装せよ。
引数無しの場合 --- 標準入力を標準出力へコピー
引数がある場合(1個以上のファイル名を引数にと
る) --- それらのファイルの内容を結合したものを
標準出力に書き出す。
catコマンドを使った場合と挙動を比較し、同じであ
ることを確認したのち提出すること。
レポートの提出方法
□ 下記のファイルを作成し、提出
• kadai3.c, kadai3.txt
□ 提出方法
システムプログラミング講義用の課題提出用フォルダ内に
あるkadai3というフォルダの中に自分の学籍番号を名前と
するフォルダを作成し、その中に上記ファイルを置く。
kadai3.txt内に学籍番号、氏名、日付、および作成したプロ
グラムの簡単な説明を記載する。
□ 提出期限
12月8日の講義開始時間まで。締め切り後に提出した場合、
成績への反映を保証しない。
参考: ライブラリ関数を使った場合
#include <stdio.h>
void filecopy (FILE *fpin,
FILE *fpout)
{
int c;
while ((c = getc(fpin))
!= EOF)
putc (c, fpout);
}
この実装では1バイトず
つコピーしている。複数
バイトまとめて読み込ん
で書き込んだ方が速い。
int main (int argc, char * argv []) {
FILE *fp;
if (argc==1)
filecopy (stdin, stdout);
else
while (--argc > 0)
if ((fp = fopen (*++argv, "r"))
== NULL) {
printf ("cat: can't open %s\n", *argv);
return 1;
} else {
filecopy (fp, stdout);
fclose (fp);
}
return 0;
}