Linuxのシステムコールとライブラリ関数・APIに関する世界観1A(システムコール)です。
ふつうのLinuxプログラミング Linuxの仕組みから学べるgccプログラミングの王道を参考にさせていただきました。
X11やGTK+/QtなどについてはLinux(X11周辺)を参照のこと。またLinuxカーネルの世界観も参照のこと。
(ふつうのLinuxプログラミング Linuxの仕組みから学べるgccプログラミングの王道を参考に執筆しました。)
自分の書いたブログ「神々とともに生きる詩人」2021/01/27より。
入出力処理は、ストリームを通じて行われる。
ストリームはバイト列のことで、バイト列としてあらわされるものは何でもストリーム。
ストリームを使うためには、デバイスファイルを媒介に使う。
キャラクタ型デバイスかブロック型デバイスかによって使い方は異なるが、キャラクタ型デバイスはバイト列の処理としてあらわされ、ランダムアクセスができない。
ブロック型デバイスは、ファイルシステムにマウントして用い、ランダムアクセスができる。
標準で標準入力、標準出力、標準エラー出力が、ストリームを識別するファイルディスクリプタとして、プロセスに与えられるが、open()/close()したファイルについてはそれぞれのストリームがファイルディスクリプタとして対応し、read()やwrite()から読み書きできる。
しかしながら、システムコールはバイト固定長の入出力しかできず、バッファ管理を行わないため速度も遅い。
これに対して、C言語のstdioライブラリでは、バッファ管理ができて高速であるほか、1文字単位や1行単位の入出力、フォーマット入出力もサポートしている。
stdioを使う時は、fopen()とfclose()を使ってファイルをオープンし、fread()やfwrite()で固定長の操作ができる。
注意点として、gets()は使ってはならない。
バッファオーバーランの脆弱性を回避できないためである。
通常、入力にはfgets()を使い、一文字単位の入出力にはgetchar()とputchar()、コンソールへの出力はputs()やprintf()、ファイルへの出力はfprintf()を使う。
また、文字列をフォーマット変換のみしたい場合は、sprintf()を使えばよい。
また、プログラミングの特性上、「一文字余計に読み込まなければトークンの終端か分からない」ということがある。
この時、一文字元に戻すAPIとしてungetc()がある。
(ふつうのLinuxプログラミング Linuxの仕組みから学べるgccプログラミングの王道を参考に執筆しました。)
UNIXにおけるプログラミングは、プロセスとファイルシステムが「ストリーム」をやりとりすることで行われる。
ストリームは要するに、バイト列の流れ道のことであり、UNIXでは標準入力やファイルを表すのにストリームという概念を使う。読みだすことを「読む」、書き込むことを「書く」という。ファイルだけではなく、デバイスやプロセスについても同様。デバイスファイルはストリームの「とっかかり」になる。
UNIXでは、バイト列のように見えるものであれば、なんでもストリームとして操作できる。そもそもが単純なバイト列の流れの操作が多かった、UNIXシステム由来の概念です。
2023.01.18編集
(ふつうのLinuxプログラミング Linuxの仕組みから学べるgccプログラミングの王道を参考に執筆しました。)
LinuxのC言語によるAPIの基本は、システムコールとライブラリ関数です。
システムコールはLinuxカーネルによる機能で、ストリーム、ファイルシステム、ネットワーク、プロセス、メモリ管理などで、カーネルの機能を使う時に使用します。
C言語のライブラリ関数は、主にglibcなどによる「標準Cライブラリ」です。
システムコールとライブラリ関数では、似たような機能を提供する関数もあります。
たとえば、システムコールのread(2), write(2), open(2), close(2)は、stdio版として、fopen(3), fclose(3), fgetc(3), fputc(3), getc(3), putc(3), getchar(3), putchar(3), ungetc(3), fgets(3), fputs(3), puts(3), printf(3), fprintf(3), scanf(3), fread(3), fwrite(3)などなど、さまざまなライブラリ関数があります。
ここで、(2)や(3)は、システムコール(2)かライブラリ関数(3)かを表しています。
システムコールは、バッファリングを行わないため、動作が遅いです。バッファリングだけの問題ではなく、ライブラリ関数よりもシステムコールの方がずっと遅いです。ライブラリ関数の方が普遍的なため、通常はライブラリ関数を優先して使いましょう。
(ふつうのLinuxプログラミング Linuxの仕組みから学べるgccプログラミングの王道を参考に執筆しました。)
システムコールでは、ファイルの情報はファイルディスクリプタと呼ばれる整数値のデータを使って保持します。これはOSがストリームの識別のために使います。
以下は四大システムコール。
API | 説明 |
---|---|
open(2) | ファイルを開く。ファイルに接続できるストリームを得る。 |
close(2) | ファイルを閉じる。ストリームを終了させる。 使い終わったストリームはclose()で閉じないといけない。 |
read(2) | ファイルを読み込む。ストリームからデータ(バイト列)を読む。 |
write(2) | ファイルを書き込む。ストリームにデータ(バイト列)を書く。 |
ファイルと言うよりは、ストリームを読み書きします。ですので、標準入出力に読み書きすることも可能です。たとえばcatコマンドを作ったり出来ます。
また、四大システムコールの美しさを保つために、それ以外の雑多な処理は全てioctl()で行う。
API | 説明 |
---|---|
ioctl(2) | デバイスに特化した色んなことをやる。 |
Linuxシステムコール・API(stdio)を参照のこと。
(ふつうのLinuxプログラミング Linuxの仕組みから学べるgccプログラミングの王道を参考に執筆しました。)
API | 説明 |
---|---|
opendir(3) | ディレクトリを開く。 |
readdir(3) | ディレクトリから1つのエントリを読む。 |
closedir(3) | ディレクトリを閉じる。 |
mkdir(2) | ディレクトリを作る。 |
umask(2) | パーミッションの設定のために値を変更する。 |
rmdir(2) | ディレクトリを削除する。 |
link(2) | ハードリンクを作る。 |
symlink(2) | シンボリックリンクを作る。 |
readlink(2) | シンボリックリンクの指し示すファイル名を知る。 |
unlink(2) | ファイル名を消す(ファイルを削除する)。 |
rename(2) | ファイルを移動する。 |
stat(2) lstat(2) | ファイルの付帯情報を知る。 |
chmod(2) | ファイルの付帯情報を変える。 |
chown(2) | ファイルのユーザーとグループを変える。 |
utime(2) | ファイルのアクセス時間と更新時間を変更する。 |
詳しくは以下の書籍が参考になります。
straceコマンドを使うと、実際に動作中のプログラムが読んだシステムコールをその場で表示してくれます。
$ strace ./hoge >/dev/null
後日注記:straceコマンドのトレース出力は標準エラー出力に出力されるが、hogeプログラムの出力は標準出力に出力される。なので、>/dev/nullとすると、hogeプログラムの出力だけを表示せず、システムコールのトレース結果だけを表示できる。
2023.05.17編集
Linuxのシステムコールのman-pagesは以下から一覧して閲覧できます。
システムコールについて。