Skip to content
Published on

[オペレーティングシステム] 02. オペレーティングシステムの構造とサービス

Authors

オペレーティングシステムのサービス

オペレーティングシステムはユーザーとプログラマに様々なサービスを提供する。これらのサービスは大きく2つのカテゴリに分けられる。

ユーザー向けサービス

1. ユーザーインターフェース(UI)

  • CLI(Command-Line Interface): テキストベースのコマンド入力。bash、zshなどのシェル
  • GUI(Graphical User Interface): マウスとアイコンベースのインターフェース。Windows、macOS
  • タッチスクリーンインターフェース: モバイルデバイスでのジェスチャーベースの操作

2. プログラム実行

システムはプログラムをメモリにロードして実行できなければならない。プログラムは正常に、または異常に(エラー表示とともに)終了することができる。

3. I/O操作

実行中のプログラムがI/Oを要求できる。セキュリティと効率性のため、ユーザープログラムが直接I/Oデバイスを制御することはできず、OSがI/O手段を提供する。

4. ファイルシステム操作

ファイルとディレクトリの読み取り、書き込み、作成、削除、検索、権限管理などを実行する。

5. 通信

プロセス間の情報交換が必要である。2つのモデルがある。

  • 共有メモリ(Shared Memory): 複数のプロセスがメモリ領域を共有
  • メッセージパッシング(Message Passing): OSを通じてプロセス間でメッセージをやり取り

6. エラー検出

CPU、メモリ、I/Oデバイス、ユーザープログラムのエラーを継続的に監視する。

システム効率のためのサービス

  • リソース割り当て: 複数のプロセスが同時に実行される際にリソースを適切に配分
  • ロギング: どのプログラムがどのリソースをどれだけ使用しているかを記録
  • 保護とセキュリティ: マルチユーザー環境でプロセス間の干渉を防止し、外部アクセスを制御

システムコール

システムコール(System Call)はOSが提供するサービスへのプログラミングインターフェースである。ユーザープログラムがカーネルの機能を要求する際に使用する。

システムコール処理の流れ

[システムコール動作フロー]

ユーザープログラム       Cライブラリ          カーネル
     |                    |                  |
     |-- open() 呼出 ---->|                  |
     |                    |-- レジスタに       |
     |                    |   システムコール番号 |
     |                    |   設定            |
     |                    |                  |
     |                    |-- trap命令 ------>|
     |                    |   (モード切替)     |
     |                    |                  |-- システムコール
     |                    |                  |   テーブルで
     |                    |                  |   ハンドラ検索
     |                    |                  |
     |                    |                  |-- sys_open() 実行
     |                    |                  |
     |                    |<-- 結果返却 ------|
     |<-- fd 返却 --------|   (モード復元)     |
     |                    |                  |

システムコールAPI

プログラマは通常、システムコールを直接呼び出さず、API(Application Programming Interface)を通じて使用する。

  • POSIX API: Unix、Linux、macOSで使用
  • Windows API: Windowsシステムで使用
  • Java API: JVMが下位OSのシステムコールに変換
// POSIX APIを使用したファイルコピーの例
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

#define BUF_SIZE 4096

int main(int argc, char *argv[]) {
    int src_fd, dst_fd;
    ssize_t bytes_read;
    char buffer[BUF_SIZE];

    // open() - ファイルシステム関連システムコール
    src_fd = open(argv[1], O_RDONLY);
    if (src_fd == -1) {
        perror("ソースファイルのオープンに失敗");
        exit(EXIT_FAILURE);
    }

    dst_fd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (dst_fd == -1) {
        perror("宛先ファイルの作成に失敗");
        exit(EXIT_FAILURE);
    }

    // read(), write() - I/O関連システムコール
    while ((bytes_read = read(src_fd, buffer, BUF_SIZE)) > 0) {
        if (write(dst_fd, buffer, bytes_read) != bytes_read) {
            perror("書き込み失敗");
            exit(EXIT_FAILURE);
        }
    }

    // close() - ファイルディスクリプタ解放システムコール
    close(src_fd);
    close(dst_fd);

    return 0;
}

システムコールの種類

分類例(POSIX)説明
プロセス制御fork(), exec(), wait(), exit()プロセスの生成、実行、終了
ファイル管理open(), read(), write(), close()ファイルのオープン、読み取り、書き込み、クローズ
デバイス管理ioctl(), read(), write()デバイスの要求、解放、読み書き
情報維持getpid(), alarm(), time()時間、日付、プロセス情報
通信pipe(), shmget(), mmap()パイプ、共有メモリ、メモリマップ
保護chmod(), chown(), umask()権限設定、所有者変更

システムコールのパラメータ渡し方法

システムコールにパラメータを渡す3つの方法がある。

  1. レジスタ: パラメータをCPUレジスタに直接渡す(最速だがレジスタ数に制限)
  2. メモリブロック: パラメータをメモリのブロックに格納し、ブロックのアドレスをレジスタで渡す
  3. スタック: プログラムがパラメータをスタックにpushし、OSがpopして使用

オペレーティングシステムの構造

モノリシック構造(Monolithic)

最もシンプルな構造で、カーネルのすべての機能が1つのアドレス空間で実行される。

[モノリシックカーネル]

+------------------------------------------+
|           ユーザープログラム                |
+------------------------------------------+
|          システムコールインターフェース       |
+------------------------------------------+
|                                          |
|  ファイルシステム | プロセス管理 | メモリ管理 |
|  ネットワーク    | デバイスドライバ | I/O管理 |
|                                          |
|      すべてカーネル空間で実行              |
+------------------------------------------+
|               ハードウェア                  |
+------------------------------------------+

: 伝統的なUNIX、Linux
  • 利点: システムコールのオーバーヘッドが少なく性能が良い
  • 欠点: 実装と保守が困難、1つのバグがシステム全体に影響

階層型構造(Layered)

OSを複数の階層に分け、各階層はすぐ下の階層の機能のみを使用する。

[階層型構造]

+-------------------+
| 階層 N: ユーザーUI |
+-------------------+
| 階層 N-1          |
+-------------------+
|       ...         |
+-------------------+
| 階層 1: メモリ管理 |
+-------------------+
| 階層 0: ハードウェア|
+-------------------+

各階層は下の階層のみ呼び出し可能
  • 利点: 実装とデバッグが容易
  • 欠点: 階層間呼び出しによるオーバーヘッド、階層定義が困難

マイクロカーネル(Microkernel)

カーネルに最小限の機能のみを残し、残りをユーザー空間に移動させる。

[マイクロカーネル構造]

ユーザー空間:
+--------+ +----------+ +---------+ +--------+
|ファイル | |デバイス   | |ネットワーク| |メモリ  |
|サーバー | |ドライバ   | |サーバー   | |サーバー |
+--------+ +----------+ +---------+ +--------+
     |          |            |          |
+------------------------------------------+
| マイクロカーネル: IPC + 基本スケジューリング + メモリ |
+------------------------------------------+
|               ハードウェア                  |
+------------------------------------------+

: Mach, QNX, MINIX 3
  • 利点: 拡張性が良く、移植が容易で、信頼性が高い
  • 欠点: ユーザー空間とカーネル空間間のメッセージパッシングオーバーヘッド

ハイブリッド構造(Hybrid)

現代のほとんどのOSは複数の構造を組み合わせたハイブリッド方式を使用する。

  • Linux: 基本的にモノリシックだが、ロード可能なカーネルモジュール(LKM)をサポート
  • Windows: マイクロカーネル設計の影響を受けているが、性能のために多くの機能をカーネルに含む
  • macOS: Machマイクロカーネル + BSD UNIXカーネル = XNUハイブリッドカーネル
[macOS/iOSのXNUカーネル]

+------------------------------------------+
|     ユーザー体験層 (Aqua/Cocoa)            |
+------------------------------------------+
|     アプリケーションフレームワーク (Core Foundation)|
+------------------------------------------+
|              カーネル環境                   |
|  +--------+  +--------+  +---------+     |
|  | Mach   |  | BSD    |  | I/O Kit |     |
|  | (IPC,  |  | (POSIX |  | (ドライ  |     |
|  | スレッド,|  |  API,  |  |)    |     |
|  | VM)    |  | ネットワーク|  |         |     |
|  +--------+  +--------+  +---------+     |
+------------------------------------------+
|               ハードウェア                  |
+------------------------------------------+

ロード可能なカーネルモジュール(LKM)

Linuxカーネルは動的にモジュールをロード/アンロードできる。

// Linuxカーネルモジュールの例 (hello.c)
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Example");
MODULE_DESCRIPTION("Hello World Kernel Module");

// モジュールロード時に呼び出し
static int __init hello_init(void) {
    printk(KERN_INFO "Hello, Kernel World!\n");
    return 0;
}

// モジュールアンロード時に呼び出し
static void __exit hello_exit(void) {
    printk(KERN_INFO "Goodbye, Kernel World!\n");
}

module_init(hello_init);
module_exit(hello_exit);
# カーネルモジュール管理コマンド
sudo insmod hello.ko    # モジュールロード
lsmod                   # ロード済みモジュール一覧
sudo rmmod hello        # モジュールアンロード
modinfo hello.ko        # モジュール情報確認

システムブート

コンピュータの電源を入れると、以下のプロセスを経てOSが起動する。

[ブートプロセス]

1. 電源 ON
      |
      v
2. BIOS/UEFI実行(ファームウェア)
   - POST(Power-On Self Test)実行
   - ハードウェア初期化
      |
      v
3. ブートローダ実行(GRUBなど)
   - ブートディスクのMBR/GPTからロード
   - カーネルイメージを選択
      |
      v
4. カーネルのロードと初期化
   - ハードウェア検出
   - ドライバ初期化
   - ルートファイルシステムのマウント
      |
      v
5. init/systemd実行(PID 1   - システムサービスの開始
   - ログインプロンプトの提供

GRUBブートローダ

GRUB(GRand Unified Bootloader)はLinuxで最も広く使用されているブートローダである。

# GRUB設定ファイルの例(/boot/grub/grub.cfgの一部)
menuentry 'Ubuntu' {
    set root='(hd0,gpt2)'
    linux /vmlinuz root=/dev/sda2 ro quiet splash
    initrd /initrd.img
}

menuentry 'Ubuntu (Recovery Mode)' {
    set root='(hd0,gpt2)'
    linux /vmlinuz root=/dev/sda2 ro recovery nomodeset
    initrd /initrd.img
}

UEFIベースのシステムでは、UEFIファームウェアがESP(EFI System Partition)からブートローダを見つけて実行する。UEFIはSecure Boot機能を提供し、署名されたブートローダのみ実行するように制限できる。


まとめ

オペレーティングシステムは様々なサービスを提供し、システムコールを通じてユーザープログラムがカーネル機能にアクセスする。OS構造はモノリシックからマイクロカーネルまで多様であり、現代のOSは性能と柔軟性の両方を追求するハイブリッド方式を採用している。ブートプロセスでは、ファームウェア、ブートローダ、カーネルが順次制御を渡しながらシステムを初期化する。