第1章: シグナルの歴史と理論的基礎

1.1 割り込み(Interrupt)の発明 - 非同期処理の起源

1.1.1 UNIVAC I と最初の割り込みシステム(1951年)

コンピュータにおける「割り込み」(interrupt)の概念は、計算機科学の最も重要な発明の一つです。1951年、UNIVAC I(Universal Automatic Computer I)が、世界初の商用割り込みシステムを実装しました。

> "The interrupt mechanism was born out of necessity. Early computers wasted enormous amounts of time waiting for slow I/O devices. The interrupt allowed the processor to do useful work while waiting." > — J. Presper Eckert, UNIVAC設計者

なぜ割り込みが必要だったのか:

初期のコンピュータはポーリング(polling)方式で周辺機器を監視していました:

【ポーリング方式の問題】

CPU: "プリンタ、準備できた?"
プリンタ: "まだ"
CPU: "プリンタ、準備できた?"
プリンタ: "まだ"
(数千回繰り返し...)
CPU: "プリンタ、準備できた?"
プリンタ: "できた!"
CPU: "やっとデータを送れる..."

→ CPUサイクルの99%が無駄な確認作業に費やされる

割り込みはこの問題を根本的に解決しました:

【割り込み方式】

CPU: "プリンタ、準備できたら教えて"(設定だけして他の仕事へ)
CPU: 別の計算を実行中...
CPU: さらに別の計算を実行中...
プリンタ: "準備できた!"(割り込み信号)
CPU: 一時停止 → プリンタにデータ送信 → 元の作業に復帰

→ CPUは生産的な仕事に集中できる

1.1.2 ハードウェア割り込みの階層構造

Abraham Silberschatz, Peter Baer Galvin, Greg Gagneの名著 "Operating System Concepts"(通称「恐竜本」、第10版、2018年)では、割り込みを以下のように分類しています:

┌─────────────────────────────────────────────────────────────┐
│                    割り込み(Interrupts)                      │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌──────────────────────┐    ┌──────────────────────┐      │
│  │ ハードウェア割り込み     │    │ ソフトウェア割り込み    │      │
│  │  (Hardware Interrupt) │    │  (Software Interrupt) │      │
│  └──────────────────────┘    └──────────────────────┘      │
│           │                           │                    │
│     ┌─────┴─────┐              ┌──────┴──────┐            │
│     ▼           ▼              ▼             ▼            │
│  外部割り込み  内部割り込み      トラップ       シグナル       │
│  (I/O機器)   (例外・障害)    (syscall)    (UNIX signal)  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

割り込みの数学的モデル:

割り込みシステムは、形式的には有限状態機械(Finite State Machine)として記述できます:

M = (Q, Σ, δ, q₀, F)

Q = {実行中, 割り込み待ち, ハンドラ実行中, 復帰処理中}
Σ = {割り込み信号, ハンドラ完了, 復帰完了}
δ : Q × Σ → Q  (状態遷移関数)
q₀ = 実行中      (初期状態)
F = {実行中}     (最終状態)

状態遷移:
  実行中 ─────割り込み信号─────→ 割り込み待ち
  割り込み待ち ─────自動─────→ ハンドラ実行中
  ハンドラ実行中 ─────ハンドラ完了─────→ 復帰処理中
  復帰処理中 ─────復帰完了─────→ 実行中

1.1.3 割り込みベクタテーブル(Interrupt Vector Table)

現代のCPUアーキテクチャでは、割り込みベクタテーブル(IVT)が割り込み処理の中核を担います:

【x86アーキテクチャの割り込みベクタテーブル】

アドレス        ベクタ番号    用途
────────────────────────────────────────────
0x0000          0           Division Error
0x0004          1           Debug Exception
0x0008          2           NMI Interrupt
0x000C          3           Breakpoint
...
0x0020          8           Double Fault
...
0x0080          32-255      ユーザー定義割り込み
                            (UNIXシグナルはこの範囲)

Maurice J. Bachは "The Design of the UNIX Operating System"(1986年)で、この仕組みをUNIXがどのように活用しているかを詳述しています:

> "UNIX transforms the hardware interrupt mechanism into a software abstraction called 'signals'. This allows user processes to respond to asynchronous events in a controlled manner."

1.2 UNIXシグナルの誕生 - Thompson と Ritchie の設計

1.2.1 UNIX Version 1 から Version 7 への進化(1971-1979)

UNIXシグナルの歴史は、UNIX自体の進化と密接に結びついています:

1971年 - UNIX Version 1(PDP-7)

  • Ken Thompson による最初のUNIX
  • シグナルの概念はまだ存在せず
  • プロセス終了は exit システムコールのみ

1973年 - UNIX Version 4(C言語で書き直し)

  • Dennis Ritchie がCでUNIXを再実装
  • 最初の原始的なシグナル機構が登場
  • kill() システムコールの原型

1979年 - UNIX Version 7(現代シグナルの原型)

  • 完全なシグナル機構の実装
  • signal() システムコールの導入
  • 15種類のシグナルが定義
  • Dennis Ritchie は1979年のBell System Technical Journalで次のように述べています:

    > "Signals provide a way to handle asynchronous events such as keyboard interrupts, program termination requests, and alarm clocks. They are, in essence, software analogs of hardware interrupts."

    1.2.2 初期シグナルの設計哲学

    Version 7 UNIXで定義された15のシグナル:

    /* UNIX Version 7 (1979) シグナル定義 */
    
    #define SIGHUP     1    /* Hangup - 端末切断 */
    #define SIGINT     2    /* Interrupt - Ctrl+C */
    #define SIGQUIT    3    /* Quit - Ctrl+\ */
    #define SIGILL     4    /* Illegal instruction */
    #define SIGTRAP    5    /* Trace trap */
    #define SIGIOT     6    /* IOT instruction */
    #define SIGEMT     7    /* EMT instruction */
    #define SIGFPE     8    /* Floating point exception */
    #define SIGKILL    9    /* Kill (cannot be caught) */
    #define SIGBUS    10    /* Bus error */
    #define SIGSEGV   11    /* Segmentation violation */
    #define SIGSYS    12    /* Bad system call */
    #define SIGPIPE   13    /* Broken pipe */
    #define SIGALRM   14    /* Alarm clock */
    #define SIGTERM   15    /* Termination signal */
    

    Thompson と Ritchie の設計には明確な思想がありました:

    【シグナル設計の3原則】
    
    1. 単純性(Simplicity)
       - シグナルは単なる「通知」であり、データを運ばない
       - ハンドラは単純な関数ポインタ
    
    2. 非同期性(Asynchrony)
       - シグナルはいつでも到着する可能性がある
       - プログラムの実行を中断する
    
    3. デフォルト動作(Default Action)
       - 各シグナルには合理的なデフォルト動作がある
       - 明示的に処理しなければデフォルトが適用される
    

    1.2.3 SIGUSR1 と SIGUSR2 の誕生

    SIGUSR1SIGUSR2 は、UNIX System III(1982年)で追加された「ユーザー定義シグナル」です:

    /* UNIX System III (1982) で追加 */
    
    #define SIGUSR1   10    /* User-defined signal 1 */
    #define SIGUSR2   12    /* User-defined signal 2 */
    

    これらのシグナルには以下の特徴があります:

  • OSによって使用されない - 完全にユーザープログラムのために予約
  • デフォルト動作はプロセス終了 - ただしキャッチ可能
  • データを運ばない - 純粋な「通知」として機能

> "SIGUSR1 and SIGUSR2 were added to give users a reliable way to send signals between their own processes without interfering with system-defined signals." > — UNIX System III Programmer's Manual, AT&T, 1982

1.3 非同期イベント処理の理論的基礎

1.3.1 イベント駆動プログラミングの起源

シグナルは「イベント駆動プログラミング」(Event-Driven Programming)の一形態です。このパラダイムは1960年代のSIMULA言語に遡ります。

同期処理 vs 非同期処理:

【同期処理(Synchronous)】

main() {
    step_1();        // 完了を待つ
    step_2();        // 完了を待つ
    step_3();        // 完了を待つ
}

時間軸: ─────step_1─────────step_2─────────step_3─────→
              ↑                ↑                ↑
           開始・終了        開始・終了        開始・終了


【非同期処理(Asynchronous)】

main() {
    register_handler(event_A, handler_A);
    register_handler(event_B, handler_B);

    while (running) {
        wait_for_event();  // イベントを待つ
    }
}

時間軸: ────────────────────────────────────────────→
              ↑         ↑    ↑       ↑
           event_A   event_B  event_A  event_B
              │         │      │       │
              ▼         ▼      ▼       ▼
           handler_A handler_B handler_A handler_B

1.3.2 コールバック関数の形式的定義

シグナルハンドラは「コールバック関数」(callback function)の一種です。形式的には:

定義: コールバック関数

f: T → U をコールバック関数とする。
システム S が f を「登録」すると、
条件 C が成立した時点で S は f を「呼び出す」。

シグナルハンドラの場合:
- f = シグナルハンドラ関数
- S = オペレーティングシステム(カーネル)
- C = 特定のシグナルの到着

C言語での表現:

/* シグナルハンドラの型定義 */
typedef void (*sighandler_t)(int);

/* コールバック登録 */
sighandler_t signal(int signum, sighandler_t handler);

/* 形式的には */
register: (Signal, Handler) → PreviousHandler
  where Signal ∈ {SIGHUP, SIGINT, ..., SIGUSR1, SIGUSR2, ...}
        Handler: Int → Void

1.3.3 Dijkstra の並行性理論との関連

Edsger W. Dijkstra は1965年の論文 "Cooperating Sequential Processes" で、並行プログラミングの理論的基礎を確立しました。シグナルはこの理論と深く関連しています:

クリティカルセクション問題:

シグナルハンドラは、Dijkstraの「クリティカルセクション問題」の
変形として理解できる:

通常のクリティカルセクション:
  process_1: enter_cs() → critical_section() → leave_cs()
  process_2: enter_cs() → critical_section() → leave_cs()

  制約: 同時に1つのプロセスのみがcs内に存在できる

シグナルハンドラの場合:
  main_code: running...
  signal_handler: 割り込み実行

  制約: ハンドラ実行中に同じシグナルをブロック
        (再入可能性の問題を回避)

相互排除(Mutual Exclusion)の必要性:

/* 危険なコード - 競合状態 */
volatile int counter = 0;

void handler(int sig) {
    counter++;  /* 非アトミック操作! */
}

int main(void) {
    signal(SIGUSR1, handler);

    while (1) {
        /* メインコードもcounterを変更する場合、
           データ競合が発生する可能性 */
        counter--;
    }
}

Dijkstra はこの問題の解決策として「セマフォ」を提案しましたが、シグナルハンドラでは sig_atomic_t 型と volatile キーワードで対処します。

1.4 シグナル配送メカニズムの詳細

1.4.1 カーネルによるシグナル管理

シグナルがどのようにプロセスに配送されるかを理解することは、Minitalkの実装において極めて重要です。

プロセス制御ブロック(PCB)内のシグナル情報:

/* Linux カーネル内部の構造(簡略化) */

struct task_struct {
    /* ... 他のフィールド ... */

    /* シグナル関連 */
    struct signal_struct    *signal;
    struct sighand_struct   *sighand;
    sigset_t                blocked;      /* ブロック中のシグナル */
    sigset_t                pending;      /* 保留中のシグナル */

    /* ... */
};

struct sigpending {
    struct list_head list;    /* 保留シグナルのリスト */
    sigset_t signal;          /* ビットマスク */
};

シグナル配送のフローチャート:

┌─────────────────────────────────────────────────────────────┐
│                  シグナル配送プロセス                          │
└─────────────────────────────────────────────────────────────┘

1. シグナル生成(Generation)
   ┌─────────────────┐
   │ kill(pid, sig)  │ ← 送信側プロセス
   └────────┬────────┘
            ▼
2. カーネル処理
   ┌─────────────────────────────────────┐
   │ カーネル空間                          │
   │                                     │
   │  ① 送信権限チェック                   │
   │     (EPERM if not allowed)          │
   │                                     │
   │  ② ターゲットプロセス検索              │
   │     (ESRCH if not found)            │
   │                                     │
   │  ③ シグナルをpendingセットに追加       │
   │     target->pending |= (1 << sig)   │
   │                                     │
   └────────┬────────────────────────────┘
            ▼
3. シグナル保留(Pending)
   ┌─────────────────────────────────────┐
   │ ターゲットプロセスのPCB               │
   │                                     │
   │  pending: [0001 0000 0000 0000]     │
   │            ↑                        │
   │          SIGUSR1 (bit 10)           │
   └────────┬────────────────────────────┘
            ▼
4. 配送タイミング
   ┌─────────────────────────────────────┐
   │ 以下のいずれかでチェック:             │
   │   - システムコールからの復帰時          │
   │   - 割り込みハンドラからの復帰時        │
   │   - スケジューラによるプロセス選択時    │
   └────────┬────────────────────────────┘
            ▼
5. ハンドラ実行
   ┌─────────────────────────────────────┐
   │ ユーザー空間                          │
   │                                     │
   │  ① コンテキスト保存                   │
   │  ② ハンドラ関数呼び出し                │
   │  ③ コンテキスト復元                   │
   │  ④ 中断した処理を再開                 │
   └─────────────────────────────────────┘

1.4.2 シグナルマスクとブロッキング

シグナルは「ブロック」することができます。ブロックされたシグナルは保留状態になり、ブロックが解除されるまで配送されません:

/* シグナルマスクの操作 */

#include <signal.h>

sigset_t mask, oldmask;

/* 空のシグナルセットを初期化 */
sigemptyset(&mask);

/* SIGUSR1をセットに追加 */
sigaddset(&mask, SIGUSR1);

/* 現在のマスクにSIGUSR1を追加(ブロック) */
sigprocmask(SIG_BLOCK, &mask, &oldmask);

/* この間、SIGUSR1は配送されない(保留される) */
/* ... クリティカルな処理 ... */

/* マスクを元に戻す(ブロック解除) */
sigprocmask(SIG_SETMASK, &oldmask, NULL);

/* 保留されていたSIGUSR1があれば、ここで配送される */

ビットマスクによるシグナル管理:

sigset_t の内部構造(概念的):

ビット位置:  0  1  2  3  4  5  6  7  8  9 10 11 12 ...
シグナル:   -- HUP INT QUIT ILL TRAP IOT EMT FPE KILL USR1 SEGV USR2 ...

例:SIGUSR1とSIGUSR2をブロック
blocked = 0b0001010000000000...
                ↑  ↑
              USR2 USR1

1.4.3 リアルタイムシグナルとの比較

POSIX.1b(1993年)では「リアルタイムシグナル」が導入されました。従来のシグナルとの比較:

┌───────────────────────────────────────────────────────────┐
│          標準シグナル vs リアルタイムシグナル                 │
├─────────────────────┬─────────────────────────────────────┤
│ 標準シグナル          │ リアルタイムシグナル                  │
│ (SIGUSR1, SIGUSR2等) │ (SIGRTMIN〜SIGRTMAX)               │
├─────────────────────┼─────────────────────────────────────┤
│ キューイングなし      │ キューイングあり                     │
│ (同一シグナルは      │ (複数の同一シグナルを               │
│  1つに統合される)    │  個別にキューイング)                │
├─────────────────────┼─────────────────────────────────────┤
│ データ添付不可        │ sigval でデータ添付可能              │
├─────────────────────┼─────────────────────────────────────┤
│ 優先順位なし          │ 番号順に優先順位あり                 │
├─────────────────────┼─────────────────────────────────────┤
│ Minitalkで使用        │ Minitalkでは使用不可                │
│ (42の要件)          │ (SIGUSR1/2のみ許可)               │
└─────────────────────┴─────────────────────────────────────┘

Minitalkで標準シグナルを使う制約は、実はプロジェクトの難易度を意図的に上げています。キューイングがないため、シグナルの送信タイミングに注意が必要です。

1.5 シグナルハンドラの制約と安全性

1.5.1 非同期シグナル安全(Async-Signal-Safe)関数

POSIXは「非同期シグナル安全」な関数を明確に定義しています。シグナルハンドラ内ではこれらの関数のみを使用できます:

/* POSIX.1-2017 で定義された非同期シグナル安全な関数(一部) */

/* ファイルI/O */
read(), write(), open(), close()

/* プロセス制御 */
_exit(), _Exit(), getpid(), getppid()
fork(), execve()

/* シグナル関連 */
signal(), sigaction(), sigprocmask()
kill(), raise()

/* その他 */
alarm(), sleep(), pause()

なぜ printf() は安全でないのか:

/*
 * printf() の内部動作(簡略化)
 */
int printf(const char *format, ...) {
    static char buffer[BUFSIZ];  /* 静的バッファ */
    static int  lock = 0;        /* 内部ロック */

    acquire_lock(&lock);         /* ロック獲得 */

    /* ここでシグナルが発生し、ハンドラ内で
     * printf() が呼ばれると... デッドロック! */

    format_to_buffer(buffer, format, ...);
    write(1, buffer, strlen(buffer));

    release_lock(&lock);         /* ロック解放 */
}

/*
 * 問題のシナリオ:
 *
 * 1. main() で printf("Hello") を呼ぶ
 * 2. printf() がロックを獲得
 * 3. SIGUSR1 到着、ハンドラに制御が移る
 * 4. ハンドラ内で printf("Signal!") を呼ぶ
 * 5. printf() がロックを獲得しようとする
 * 6. ロックは既に main() の printf() が保持
 * 7. デッドロック - 永久に待機
 */

1.5.2 再入可能性(Reentrancy)

シグナルハンドラは「再入可能」(reentrant)である必要があります:

/* 再入不可能な関数の例 */
static char *buffer = NULL;

char *dangerous_function(void) {
    if (buffer == NULL)
        buffer = malloc(100);  /* 静的/グローバル状態を変更 */

    strcpy(buffer, "data");
    return buffer;
}

/*
 * 問題:
 * 1. main() が dangerous_function() を呼ぶ
 * 2. malloc() 実行中にシグナル到着
 * 3. ハンドラが dangerous_function() を呼ぶ
 * 4. 最初の呼び出しの buffer が上書きされる
 * 5. main() に戻ると、データが破損している
 */


/* 再入可能な関数の例 */
void safe_function(char *buffer, size_t size) {
    /* ローカル変数とパラメータのみを使用 */
    /* 静的/グローバル状態を変更しない */
    strncpy(buffer, "data", size);
}

1.5.3 volatile と sig_atomic_t

シグナルハンドラとメインコード間でデータを共有する場合、volatilesig_atomic_t が重要です:

/*
 * volatile キーワード
 *
 * コンパイラに「この変数はプログラムの外部から変更される可能性がある」
 * ことを伝える。最適化による予期せぬ動作を防ぐ。
 */

/* 問題のあるコード */
int flag = 0;

void handler(int sig) {
    flag = 1;  /* フラグを設定 */
}

int main(void) {
    signal(SIGUSR1, handler);

    /*
     * コンパイラは最適化により、flag を一度だけ読み込み、
     * レジスタにキャッシュする可能性がある。
     * その場合、handler() による変更が見えない!
     */
    while (flag == 0) {
        /* 無限ループになる可能性 */
    }
}


/* 正しいコード */
volatile sig_atomic_t flag = 0;

void handler(int sig) {
    flag = 1;
}

int main(void) {
    signal(SIGUSR1, handler);

    /*
     * volatile により、毎回メモリから読み込む。
     * sig_atomic_t により、アトミックな読み書きが保証される。
     */
    while (flag == 0) {
        pause();  /* シグナルを待つ */
    }
}

sig_atomic_t の保証:

sig_atomic_t は以下を保証する:

1. 読み込み/書き込みがアトミック(分割されない)
2. シグナルハンドラとメインコード間で安全に共有可能
3. 最低限 int のサイズ以上

ただし、複合操作(例:flag++)はアトミックではない!

正しい使い方:
  flag = 0;          /* OK */
  flag = 1;          /* OK */
  x = flag;          /* OK */

危険な使い方:
  flag++;            /* アトミックではない! */
  flag = flag + 1;   /* アトミックではない! */

1.6 Minitalkプロジェクトの概要

1.6.1 プロジェクトの位置づけ

ここまでの理論的背景を踏まえて、Minitalkプロジェクトを理解しましょう。

Minitalkは42 Schoolのカリキュラムにおいて、UNIXシグナルを使った最も原始的なプロセス間通信(IPC) を実装するプロジェクトです。

【42のIPC関連プロジェクト進行】

Minitalk        シグナルベースIPC(最も原始的)
    ↓
Pipex           パイプベースIPC(より高速)
    ↓
Minishell       複合IPC(パイプ、リダイレクト)
    ↓
(その他)        ソケット、共有メモリ等

1.6.2 プロジェクト要件

基本要件:

【サーバー】
- 起動時にPIDを表示
- クライアントからの文字列を受信して表示
- 複数のクライアントからのメッセージを連続処理

【クライアント】
- コマンドライン引数:サーバーPID、送信文字列
- シグナル(SIGUSR1、SIGUSR2)で文字列を送信

【制約】
- 使用可能なシグナル:SIGUSR1とSIGUSR2のみ
- グローバル変数:1つまで
- libft使用可能

許可された関数:

/* システムコール */
kill()          /* シグナル送信 */
signal()        /* シグナルハンドラ設定(非推奨) */
sigaction()     /* シグナルハンドラ設定(推奨) */
sigemptyset()   /* シグナルセット初期化 */
sigaddset()     /* シグナルセットに追加 */
pause()         /* シグナル待機 */
usleep()        /* マイクロ秒スリープ */
getpid()        /* PID取得 */

/* 標準I/O(libft経由または直接) */
write()

/* その他(libft経由) */
malloc(), free(), exit()

1.6.3 通信プロトコルの設計

Minitalkの核心は、2つのシグナルだけでデータを伝送することです:

【基本アイデア】

情報理論的には、2種類のシグナルで1ビットを表現できる:
  SIGUSR1 → 0
  SIGUSR2 → 1

1文字(8ビット)を送信するには、8つのシグナルが必要:

例:'A' (ASCII 65 = 0b01000001) を送信

  ビット:  0  1  0  0  0  0  0  1
  シグナル: U1 U2 U1 U1 U1 U1 U1 U2
           ↑  ↑  ↑  ↑  ↑  ↑  ↑  ↑
           7  6  5  4  3  2  1  0  ← ビット位置

シンプルなプロトコル図:

┌──────────────────────────────────────────────────────────┐
│                 Minitalk 通信プロトコル                    │
└──────────────────────────────────────────────────────────┘

クライアント                              サーバー
     │                                      │
     │  文字 'H' = 0b01001000 を送信         │
     │                                      │
     │──── SIGUSR1 (bit 7 = 0) ────────────→│
     │                                      │ bit_count++
     │──── SIGUSR2 (bit 6 = 1) ────────────→│
     │                                      │ bit_count++
     │──── SIGUSR1 (bit 5 = 0) ────────────→│
     │                                      │ bit_count++
     │──── SIGUSR1 (bit 4 = 0) ────────────→│
     │                                      │ bit_count++
     │──── SIGUSR2 (bit 3 = 1) ────────────→│
     │                                      │ bit_count++
     │──── SIGUSR1 (bit 2 = 0) ────────────→│
     │                                      │ bit_count++
     │──── SIGUSR1 (bit 1 = 0) ────────────→│
     │                                      │ bit_count++
     │──── SIGUSR1 (bit 0 = 0) ────────────→│
     │                                      │ 8ビット完了!
     │                                      │ char = 'H'
     │                                      │ write(1, &c, 1)
     │                                      │

1.6.4 シグナルロスの問題と対策

理論セクションで説明したように、標準シグナルはキューイングされません。この問題への対処が Minitalk 実装の鍵です:

【問題:高速送信によるシグナルロス】

クライアントが連続して送信:
  t=0ms:  kill(pid, SIGUSR1)
  t=0ms:  kill(pid, SIGUSR2)  ← 前のシグナルと「衝突」
  t=0ms:  kill(pid, SIGUSR1)  ← さらに衝突

サーバー側で受信されるのは1つだけ(または0)


【対策1:固定遅延(usleep)】

  kill(pid, signal);
  usleep(100);  /* 100マイクロ秒待機 */

  利点:実装が簡単
  欠点:遅延時間の最適値が環境依存


【対策2:ACKメカニズム(ボーナス)】

  クライアント              サーバー
       │                      │
       │── シグナル ─────────→│
       │                      │ 処理
       │←── ACKシグナル ─────│
       │                      │
       │── 次のシグナル ─────→│
       │                      │

  利点:確実な通信
  欠点:実装が複雑、速度が半減

1.7 実装への準備

1.7.1 基本的なサーバー骨格

/* server.c - 骨格のみ */

#include <signal.h>
#include <unistd.h>

/* グローバル変数(1つまで許可) */
volatile sig_atomic_t g_data = 0;

void signal_handler(int signum, siginfo_t *info, void *context)
{
    /* シグナル処理 */
    (void)context;
    (void)info;

    if (signum == SIGUSR1)
        ; /* ビット0の処理 */
    else if (signum == SIGUSR2)
        ; /* ビット1の処理 */
}

int main(void)
{
    struct sigaction sa;
    pid_t pid;

    /* PIDを表示 */
    pid = getpid();
    /* ft_putnbr(pid); */

    /* sigactionを設定 */
    sa.sa_sigaction = signal_handler;
    sa.sa_flags = SA_SIGINFO;
    sigemptyset(&sa.sa_mask);

    sigaction(SIGUSR1, &sa, NULL);
    sigaction(SIGUSR2, &sa, NULL);

    /* シグナルを待ち続ける */
    while (1)
        pause();

    return (0);
}

1.7.2 基本的なクライアント骨格

/* client.c - 骨格のみ */

#include <signal.h>
#include <unistd.h>

void send_bit(pid_t pid, int bit)
{
    if (bit == 0)
        kill(pid, SIGUSR1);
    else
        kill(pid, SIGUSR2);
    usleep(100);  /* 安全マージン */
}

void send_char(pid_t pid, char c)
{
    int bit_index;

    bit_index = 7;
    while (bit_index >= 0)
    {
        send_bit(pid, (c >> bit_index) & 1);
        bit_index--;
    }
}

void send_string(pid_t pid, const char *str)
{
    while (*str)
    {
        send_char(pid, *str);
        str++;
    }
    send_char(pid, '\0');  /* 終端を送信 */
}

int main(int argc, char **argv)
{
    pid_t server_pid;

    if (argc != 3)
    {
        /* usage表示 */
        return (1);
    }

    server_pid = /* ft_atoi(argv[1]) */;
    send_string(server_pid, argv[2]);

    return (0);
}

1.8 まとめと次章への準備

この章では、シグナルの歴史的背景と理論的基礎を学びました:

学んだこと

  • 割り込みの発明(1951年、UNIVAC I)
- ポーリングからイベント駆動への進化 - CPUの効率的な利用

  • UNIXシグナルの誕生(1971-1979年)
- Thompson と Ritchie の設計思想 - SIGUSR1/SIGUSR2 の導入(1982年)

  • 非同期処理の理論
- Dijkstra の並行性理論 - クリティカルセクションと相互排除

  • シグナル配送メカニズム
- カーネルによる管理 - ビットマスクとブロッキング

  • シグナルハンドラの制約
- 非同期シグナル安全関数 - volatile と sig_atomic_t

  • Minitalkプロジェクトの概要
- 2つのシグナルによる1ビット通信 - シグナルロスの問題と対策

次章の予告

第2章では、情報理論とビット操作について学びます:

  • Claude Shannon の情報理論
  • ビット操作の数学的基礎
  • ASCII と Unicode のエンコーディング
  • Minitalkでのビットシリアライゼーション

---

参考文献:

  • Silberschatz, A., Galvin, P. B., & Gagne, G. (2018). Operating System Concepts (10th ed.). Wiley.
  • Bach, M. J. (1986). The Design of the UNIX Operating System. Prentice Hall.
  • Dijkstra, E. W. (1965). Cooperating Sequential Processes. Technical Report EWD-123.
  • Ritchie, D. M., & Thompson, K. (1974). "The UNIX Time-Sharing System". Communications of the ACM, 17(7), 365-375.
  • POSIX.1-2017. IEEE Standard for Information Technology. IEEE.