[C言語のはじめ方] Part39: システムコール(open, read, write)

C言語

オペレーティングシステムと直接対話する方法を学ぼう!

はじめに:システムコールって何? 🤔

こんにちは!C言語学習の旅、順調ですか?今回は、普段使っている printfscanf よりも、もっとコンピュータの深い部分、つまりオペレーティングシステム(OS)と直接やり取りする方法を探求します。それがシステムコールです。

システムコールは、アプリケーション(私たちが書くプログラム)がOSの機能(ファイル操作、プロセス管理、ネットワーク通信など)を利用するための「お願い」をするための特別な関数呼び出しです。これにより、ハードウェアに近いレベルでの操作が可能になります。

今回は、ファイル操作の基本となる以下の3つの重要なシステムコールを学びます。

  • open: ファイルを開く
  • read: ファイルからデータを読み込む
  • write: ファイルにデータを書き込む

これらは、OSが提供する低レベルなファイルI/O機能です。stdio.h の関数(fopen, fread, fwrite など)は、内部でこれらのシステムコールを使っていますが、システムコールを直接使うことで、より細かい制御が可能になります。🚀

💡 ポイント: システムコールはOSとの橋渡し役!ファイル操作の基礎を理解しよう。

1. open システムコール:ファイルへの扉を開く 🚪

ファイルに対する読み書きを行う前に、まずファイルを「開く」必要があります。その役割を担うのが open システムコールです。

基本的な使い方

open を使うには、fcntl.h ヘッダファイルをインクルードする必要があります。(環境によっては sys/types.h, sys/stat.h も必要)

#include <fcntl.h> // open 関数の定義
#include <unistd.h> // close など他のシステムコールで使うことも
#include <stdio.h>  // perror のため
#include <errno.h>  // errno のため

int main() {
    // open(const char *pathname, int flags); // 最もシンプルな形式
    // open(const char *pathname, int flags, mode_t mode); // ファイル作成時に使用

    int fd = open("example.txt", O_RDWR | O_CREAT, 0644); // 読み書きモードで、なければ作成

    if (fd == -1) {
        perror("open failed"); // エラーメッセージを表示
        return 1; // エラー終了
    }

    printf("File opened successfully! File descriptor: %d\n", fd);

    // ... ファイル操作 ...

    // ファイルを閉じる(後述)
    if (close(fd) == -1) {
        perror("close failed");
        return 1;
    }

    printf("File closed.\n");
    return 0;
}

引数の説明

引数 説明 備考
pathname 開きたいファイルの名前(パス)を指定する文字列。 例: "data.log", "/home/user/document.txt"
flags ファイルの開き方を指定するフラグ。複数のフラグは | (ビットOR) で組み合わせる。 よく使うフラグ:
  • O_RDONLY: 読み込み専用
  • O_WRONLY: 書き込み専用
  • O_RDWR: 読み書き両用
  • O_CREAT: ファイルが存在しない場合に新規作成する
  • O_TRUNC: ファイルが存在する場合、中身を空にする(書き込み権限が必要)
  • O_APPEND: 追記モード(書き込みは常にファイルの末尾に行われる)
mode flagsO_CREAT を指定した場合に、新しく作成されるファイルのアクセス権を指定する。8進数で指定することが多い。 例: 0644 (所有者は読み書き可、その他は読み取り専用)
この引数は O_CREAT がない場合は無視される。

戻り値

  • 成功時: ファイルディスクリプタ (File Descriptor, fd) と呼ばれる非負の整数が返される。これは、後続の read, write, close などで、開いたファイルを識別するために使われる。
  • 失敗時: -1 が返され、グローバル変数 errno にエラーの種類を示す値が設定される。perror 関数を使うと、errno に対応するエラーメッセージを表示できる。
⚠️ 注意: ファイルディスクリプタはOSが管理するリソースです。使い終わったら必ず close システムコールで閉じましょう!

2. read システムコール:ファイルから情報を得る 📖

open でファイルを開いたら、次はそのファイルからデータを読み込みます。そのためのシステムコールが read です。

基本的な使い方

read を使うには、unistd.h ヘッダファイルをインクルードする必要があります。

#include <unistd.h> // read 関数の定義
#include <fcntl.h>  // open, O_RDONLY
#include <stdio.h>   // printf, perror
#include <errno.h>   // errno
#include <stdlib.h>  // exit

#define BUFFER_SIZE 1024 // 読み込みバッファのサイズ

int main() {
    char buffer[BUFFER_SIZE]; // 読み込んだデータを入れる場所
    int fd;
    ssize_t bytes_read; // 読み込んだバイト数を格納する型 (signed size_t)

    fd = open("readme.txt", O_RDONLY); // 読み込み専用でファイルを開く
    if (fd == -1) {
        perror("open failed");
        return 1;
    }

    // ファイルからデータを読み込む
    // read(ファイルディスクリプタ, データを格納するバッファ, 最大読み込みバイト数);
    bytes_read = read(fd, buffer, BUFFER_SIZE - 1); // バッファオーバーフローを防ぐため-1

    if (bytes_read == -1) {
        perror("read failed");
        close(fd); // エラー時もファイルは閉じる
        return 1;
    } else if (bytes_read == 0) {
        printf("End of file reached.\n");
    } else {
        buffer[bytes_read] = '\0'; // 読み込んだデータを文字列として扱うためにNULL終端する
        printf("Read %zd bytes: \n---\n%s\n---\n", bytes_read, buffer);
    }

    if (close(fd) == -1) {
        perror("close failed");
        return 1;
    }

    return 0;
}

引数の説明

引数 説明
fd open で取得したファイルディスクリプタ。
buf 読み込んだデータを格納するためのメモリ領域(バッファ)へのポインタ。
count 読み込みたい最大のバイト数。buf のサイズを超えないように注意!

戻り値

  • 成功時: 実際に読み込んだバイト数が返される (0 以上 count 以下)。
  • ファイルの終端 (EOF): 0 が返される。これ以上読み込むデータがないことを示す。
  • 失敗時: -1 が返され、errno にエラーコードが設定される。
💡 ポイント: read の戻り値は重要!読み込んだバイト数、EOF(0)、エラー(-1) をしっかりチェックしよう。大きなファイルを扱う場合は、ループで繰り返し read を呼び出す必要がある。

3. write システムコール:ファイルに情報を記録する ✍️

ファイルにデータを書き込むには write システムコールを使います。

基本的な使い方

writeunistd.h ヘッダファイルが必要です。

#include <unistd.h> // write 関数の定義
#include <fcntl.h>  // open, O_WRONLY, O_CREAT, O_TRUNC
#include <stdio.h>   // printf, perror
#include <errno.h>   // errno
#include <string.h>  // strlen

int main() {
    int fd;
    char *message = "Hello from write system call!\n";
    ssize_t bytes_written; // 書き込んだバイト数を格納する型

    // 書き込み専用でファイルを開く。存在しなければ作成、存在すれば中身を空にする。
    fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror("open failed");
        return 1;
    }

    // ファイルにデータを書き込む
    // write(ファイルディスクリプタ, 書き込むデータへのポインタ, 書き込むバイト数);
    bytes_written = write(fd, message, strlen(message));

    if (bytes_written == -1) {
        perror("write failed");
        close(fd);
        return 1;
    }

    printf("Wrote %zd bytes to output.txt.\n", bytes_written);

    if (close(fd) == -1) {
        perror("close failed");
        return 1;
    }

    return 0;
}

引数の説明

引数 説明
fd open で取得した、書き込み権限のあるファイルディスクリプタ。
buf 書き込みたいデータが格納されているメモリ領域へのポインタ。
count 書き込みたいバイト数。

戻り値

  • 成功時: 実際に書き込んだバイト数が返される。通常は count と同じ値になるはずだが、ディスク容量不足などで少なくなる可能性もある。
  • 失敗時: -1 が返され、errno にエラーコードが設定される。
⚠️ 注意: write は要求したバイト数すべてを一度に書き込めない場合があります。戻り値を確認し、必要であれば残りのデータを書き込むループ処理が必要です。

4. close システムコール:後片付けは忘れずに 👋

open で開いたファイルは、使い終わったら必ず close システムコールで閉じる必要があります。これにより、OSに使用していたリソース(ファイルディスクリプタなど)を返却します。

基本的な使い方

closeunistd.h ヘッダファイルが必要です。

#include <unistd.h> // close 関数の定義
#include <fcntl.h>  // open
#include <stdio.h>   // perror

int main() {
    int fd = open("temp.txt", O_CREAT | O_WRONLY, 0600);
    if (fd == -1) {
        perror("open failed");
        return 1;
    }

    printf("File opened (fd: %d). Now closing...\n", fd);

    // ファイルを閉じる
    if (close(fd) == -1) {
        perror("close failed");
        return 1; // エラー終了
    }

    printf("File closed successfully.\n");
    return 0;
}

引数

  • fd: 閉じたいファイルのファイルディスクリプタ。

戻り値

  • 成功時: 0
  • 失敗時: -1 (errno にエラーコードが設定される)
🚨 重要: ファイルを閉じ忘れると、リソースリーク(使用可能なファイルディスクリプタの枯渇など)の原因になります。open したら、必ず対応する close を適切な場所(正常終了時、エラー発生時など)で呼び出しましょう。

まとめ ✨

今回は、OSと直接対話するための基本となるシステムコール open, read, write, そして close を学びました。

  • open: ファイルを開き、ファイルディスクリプタを取得する。
  • read: ファイルディスクリプタを使ってファイルからデータを読み込む。
  • write: ファイルディスクリプタを使ってファイルへデータを書き込む。
  • close: 使い終わったファイルディスクリプタを閉じてリソースを解放する。

これらのシステムコールは、C言語でのファイル操作の根幹をなすものです。stdio.h の関数と比べて低レベルで扱いにくい面もありますが、より細かい制御ができるというメリットがあります。エラー処理(perrorerrno の確認)とリソース管理(close の呼び出し)をしっかり行うことが重要です。📚

システムコールを理解することで、プログラムがOS上でどのように動作しているのか、より深く知ることができます。頑張ってマスターしましょう!💪

参考情報 🔗

より詳しい情報は、お使いのシステムのmanページで確認できます。ターミナルで以下のコマンドを実行してみてください。

  • man 2 open
  • man 2 read
  • man 2 write
  • man 2 close

Web上でmanページを参照できるサイトもあります。

コメント

タイトルとURLをコピーしました