この記事内では便宜上カーネルのシステムコールを呼び出す関数のこともシステムコールと書いています
open()システムコール
ファイルのopenには open() システムコールを使います
以下のようなファイルを用意してみました。
#include <stdio.h> #include <sys/stat.h> #include <fcntl.h> int main(){ int fd; fd = open("./test.txt", O_RDWR); // O_RDWR は読み取りと書き込みモード if (fd == -1){ printf("error!\n"); } printf("fd is %d\n", fd); return 0; }
open() はファイルディスクリプタを返します。ファイルディスクリプタはあまり大きくない整数で、 -1 はエラーを表します。
まずはopenするファイルがない状態で実行します
$ cc opentest.c $ ./a.out error! fd is -1
想定通り、open()は-1を返していました。
次はopen()するファイルを用意して実行します
$ touch test.txt $ ./a.out fd is 3
ファイルディスクリプタは 3 だったようです。
open() すると、ファイルディスクリプタは常に利用できる最小の値が割り当てられます。
何度実行しても 3 が返ります。
$ ./a.out fd is 3 $ ./a.out fd is 3 $ ./a.out fd is 3
read()システムコール
ssize_t read(int fd, void *buffer, size_t count)
read()は、引数にファイルディスクリプタ、読み取ったデータを格納するメモリバッファアドレス、読み取るバイト数 を指定します。 返り値は、成功時には読み取ったバイト数、エラー時には-1です。
※ちなみに、読み取るバイト数より実際のファイルのバイト数が小さい場合は、読み取れるだけ読み取ります。
open()で返ってきたfdをread()してみます
#include <stdio.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(){ int fd; fd = open("./test.txt", O_RDWR); if (fd == -1){ printf("error!\n"); } printf("fd is %d\n", fd); // ここまではopen()と同じ char buffer[100]; ssize_t numRead = read(fd, buffer, 100); printf("numRead is %d\n", numRead); printf(buffer); return 0; }
まずはファイルをtouchしたばかりの状態で試します
$ cc readtest.c $ ./a.out fd is 3 numRead is 0
想定どおり、fd: 3のファイルから読み取ったバイト数は0ですね
次はなにか適当に文字列を入れた状態で試してみます
$ echo "is this a pen?" > test.txt $ ./a.out fd is 3 numRead is 15 is this a pen?
read()は読み取ったバイト数である、15を返してきました。bufferにもデータが格納されていることがわかります。
write()システムコール
ssize_t write(int fd, void *buffer, size_t count);
read()に似てますね
#include <stdio.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(){ int fd; fd = open("./test.txt", O_RDWR); if (fd == -1){ printf("error!\n"); } printf("fd is %d\n", fd); char buffer[7] = "abcdefg"; ssize_t numWrite = write(fd, buffer, 3); printf("numWrite is %d\n", numWrite); printf(buffer); return 0; }
bufferは"abcdefg"ですが、countが3バイトなので、abcだけがwriteされます。
$ cc writetest.c $ ./a.out fd is 3 numWrite is 3 abcdefg $ cat test.txt abc
close()システムコール
int close(int fd)
返り値はcloseしたfdです
#include <stdio.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(){ int fd; fd = open("./test.txt", O_RDWR); if (fd == -1){ printf("error!\n"); } printf("fd is %d\n", fd); int closed = close(fd); printf("closed %d\n", fd); return 0; }
$ cc closetest.c $ ./a.out fd is 3 closed 3
lseek()システムコール
off_t lseek(int fd, off_t offset, int whence)
offsetにはバイト数を指定、whenceにはSEEK_SET, SEEK_CUR, SEEK_ENDから指定します。
whenceがoffsetの原点になります。
まずはテスト用にファイルを準備します
$ echo "abcdefghij" > test.txt
以下は先頭をwhenceに指定して、offset 3でファイルを読み取る例です
#include <stdio.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(){ int fd; fd = open("./test.txt", O_RDWR); if (fd == -1){ printf("error!\n"); } printf("fd is %d\n", fd); lseek(fd, 3, SEEK_SET); char buffer[100]; ssize_t numRead = read(fd, buffer, 100); printf("numRead is %d\n", numRead); printf(buffer); return 0; }
d以降が読み取られていることがわかります
$ cc lseektest.c $ ./a.out fd is 3 numRead is 8 defghij
ファイルホール
ファイル末尾を超えた位置へシークしてread()すると、戻り値は0(end-of-file)になりますが、書き込みは可能です。
ファイル末尾からそれよりも後ろに新たに書き込まれた位置までをファイルホールといいます。
まずはテスト用にファイルを準備します
$ echo "abcdefghij" > test.txt
このファイルでは、whenceをSEEK_SET, offsetを11以上にするとファイル末尾を超えます。
試しにoffsetに20を設定してread()してみます
#include <stdio.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(){ int fd; fd = open("./test.txt", O_RDWR); if (fd == -1){ printf("error!\n"); } lseek(fd, 20, SEEK_SET); char buffer[100]; ssize_t numRead = read(fd, buffer, 100); printf("numRead is %d\n", numRead); printf(buffer); return 0; }
読み取ったバイト数は0になります
$ cc lseektest.c $ ./a.out numRead is 0
次は書き込みです
10バイトしか書き込まれていないファイルに対して、offset 20で"aaa"を書き込みます
#include <stdio.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(){ int fd; fd = open("./test.txt", O_RDWR); if (fd == -1){ printf("error!\n"); } lseek(fd, 20, SEEK_SET); char buffer[3] = "aaa"; ssize_t numWritten = write(fd, buffer, 3); printf("numWritten is %d\n", numWritten); printf(buffer); return 0; }
出力は通常の書き込みと同じです
$ cc lseektest.c $ ./a.out numWritten is 3 aaa
ファイルの中は以下のようになっています
$ cat test.txt abcdefghij aaa
abcdefghij と aaaの間(ファイルホール)は改行コードがあるように見えますが実際にはNULLです。
ファイルシステムはファイルホールに対してはディスクブロックを割り当てず、あとでデータが書き込まれた時点で初めて割り当てます。少ないブロック数で済むのでファイルホールはディスク領域の節約にも繋がります。
コアダンプファイルはファイルホールを持つ代表例です。