第3章: POSIX シグナル標準と sigaction

3.1 POSIX 標準化の歴史

3.1.1 UNIX の分裂と標準化への道

1980年代、UNIX は多くの派生版に分裂し、深刻な互換性問題を引き起こしていました:

【1980年代の UNIX 混乱期】

AT&T UNIX (System V)
    ├── AIX (IBM)
    ├── HP-UX (HP)
    └── Solaris (Sun)

BSD UNIX (Berkeley)
    ├── SunOS (Sun)
    ├── FreeBSD
    └── NetBSD

問題: 同じプログラムが異なる UNIX で動かない!
      特にシグナル処理の動作が大きく異なっていた

例: signal() の動作
  - System V: ハンドラ呼び出し後、デフォルトにリセット
  - BSD: ハンドラを維持(リセットしない)

→ 移植可能なプログラムを書くことが困難

3.1.2 IEEE POSIX の誕生(1988年)

POSIX(Portable Operating System Interface)は、IEEE が策定した UNIX 標準です:

> "POSIX is a family of standards specified by the IEEE Computer Society for maintaining compatibility between operating systems." > — IEEE Std 1003.1

主要な POSIX 標準:

POSIX.1 (1988) - 基本OS インターフェース
  ├── ファイルシステム
  ├── プロセス管理
  ├── シグナル処理 ← sigaction() の標準化
  └── 基本I/O

POSIX.1b (1993) - リアルタイム拡張
  ├── リアルタイムシグナル (SIGRTMIN-SIGRTMAX)
  ├── メッセージキュー
  └── セマフォ

POSIX.1c (1995) - スレッド (Pthreads)
  ├── スレッド管理
  └── スレッドとシグナルの相互作用

POSIX.1-2001/2008/2017 - 統合標準
  └── Single UNIX Specification (SUS) との統合

3.1.3 signal() の問題点

古い signal() 関数には、POSIX で是正された多くの問題がありました:

/*
 * signal() の問題点
 */

/* 問題1: 動作が実装依存 */
void handler(int sig)
{
    /*
     * System V: この関数の呼び出し後、ハンドラはリセットされる
     *           → 次のシグナルでプロセスが終了する可能性
     *
     * BSD: ハンドラは維持される
     *      → 期待通りの動作
     */
}

/* 問題2: 競合状態 */
void unreliable_handler(int sig)
{
    /* System V で再登録が必要 */
    signal(SIGUSR1, unreliable_handler);

    /* ↑ この再登録の前に次のシグナルが来たら?
     *   → シグナルがロストまたはデフォルト動作で処理
     */

    /* 実際の処理 */
}

/* 問題3: システムコールの中断 */
/*
 * read() 実行中にシグナルを受信:
 *   - 一部の実装: read() は EINTR で失敗
 *   - 他の実装: read() は自動的に再開
 *
 * → 移植可能なエラー処理が困難
 */

3.2 sigaction() の設計思想

3.2.1 信頼性の高いシグナル処理

sigaction() は、signal() の問題を解決するために設計されました:

#include <signal.h>

int sigaction(int signum,
              const struct sigaction *act,
              struct sigaction *oldact);

設計原則:

1. 一貫性(Consistency)
   - すべての POSIX システムで同じ動作を保証
   - 実装依存の動作を排除

2. 原子性(Atomicity)
   - ハンドラの登録と設定が原子的に行われる
   - 競合状態を防止

3. 制御性(Controllability)
   - フラグによる細かい動作制御
   - シグナルマスクの明示的な管理

4. 拡張性(Extensibility)
   - siginfo_t による追加情報の取得
   - 将来の拡張に対応可能な構造

3.2.2 struct sigaction の構造

struct sigaction {
    /* ハンドラ関数(2種類から選択) */
    void     (*sa_handler)(int);
    void     (*sa_sigaction)(int, siginfo_t *, void *);

    /* ハンドラ実行中にブロックするシグナル */
    sigset_t   sa_mask;

    /* 動作フラグ */
    int        sa_flags;

    /* 内部使用(設定不要) */
    void     (*sa_restorer)(void);
};

各フィールドの詳細:

┌─────────────────────────────────────────────────────────────┐
│              struct sigaction のフィールド                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  sa_handler または sa_sigaction                              │
│  ├── sa_handler: void (*)(int)                             │
│  │   シグナル番号のみを受け取る簡易ハンドラ                    │
│  │                                                         │
│  └── sa_sigaction: void (*)(int, siginfo_t *, void *)      │
│      詳細情報を受け取る拡張ハンドラ                           │
│      (SA_SIGINFO フラグが必要)                             │
│                                                             │
│  sa_mask                                                    │
│  └── ハンドラ実行中に追加でブロックするシグナル                │
│      (処理中のシグナルは自動的にブロック)                    │
│                                                             │
│  sa_flags                                                   │
│  └── ハンドラの動作を制御するビットフラグ                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

3.2.3 sa_flags の詳細

POSIX で定義されている主要なフラグ:

/*
 * SA_SIGINFO
 *
 * sa_sigaction ハンドラを使用し、siginfo_t で
 * 詳細情報を受け取る
 */
sa.sa_flags = SA_SIGINFO;
/* → sa_sigaction(int, siginfo_t *, void *) を使用 */


/*
 * SA_RESTART
 *
 * シグナルによって中断されたシステムコールを
 * 自動的に再開する
 */
sa.sa_flags = SA_RESTART;
/*
 * 効果:
 *   read(), write(), etc. がシグナルで中断されても
 *   EINTR を返さずに自動再開
 */


/*
 * SA_NODEFER (SA_NOMASK)
 *
 * ハンドラ実行中に同じシグナルをブロックしない
 * 危険: 再帰的なハンドラ呼び出しの可能性
 */
sa.sa_flags = SA_NODEFER;
/* 通常は使用しない(デフォルトでブロックされる) */


/*
 * SA_RESETHAND (SA_ONESHOT)
 *
 * ハンドラ呼び出し後、デフォルト動作に戻す
 * (signal() の System V 動作を再現)
 */
sa.sa_flags = SA_RESETHAND;
/* 通常は使用しない */


/*
 * SA_NOCLDSTOP
 *
 * 子プロセスが停止した時に SIGCHLD を受け取らない
 * (終了時のみ通知)
 */
sa.sa_flags = SA_NOCLDSTOP;
/* SIGCHLD ハンドラでのみ意味がある */

3.3 siginfo_t 構造体の詳細

3.3.1 シグナルの詳細情報

SA_SIGINFO フラグを使用すると、シグナルに関する詳細情報を取得できます:

typedef struct {
    int      si_signo;    /* シグナル番号 */
    int      si_errno;    /* エラー番号(ある場合) */
    int      si_code;     /* シグナルコード(詳細理由) */

    pid_t    si_pid;      /* 送信元プロセスID */
    uid_t    si_uid;      /* 送信元ユーザーID */

    void    *si_addr;     /* フォールトアドレス(SIGSEGV等) */
    int      si_status;   /* 終了コード/シグナル(SIGCHLD) */

    long     si_band;     /* バンドイベント(SIGPOLL) */

    union sigval si_value; /* シグナル付随データ */
} siginfo_t;

3.3.2 si_code の意味

si_code フィールドは、シグナルが発生した理由を示します:

/*
 * 汎用コード(すべてのシグナルで使用可能)
 */
SI_USER       /* kill() または raise() で送信 */
SI_KERNEL     /* カーネルが送信 */
SI_QUEUE      /* sigqueue() で送信 */
SI_TIMER      /* タイマー期限切れ */

/*
 * SIGILL (不正命令) の場合
 */
ILL_ILLOPC    /* 不正なオペコード */
ILL_ILLTRP    /* 不正なトラップ */
ILL_PRVOPC    /* 特権命令 */

/*
 * SIGSEGV (セグメンテーション違反) の場合
 */
SEGV_MAPERR   /* マッピングされていないアドレス */
SEGV_ACCERR   /* マッピングの権限違反 */

/*
 * Minitalk での主要な使用例
 */
void handler(int sig, siginfo_t *info, void *ctx)
{
    if (info->si_code == SI_USER)
    {
        /* kill() で送信されたシグナル */
        pid_t sender = info->si_pid;
        /* sender のPID を使用して応答可能 */
    }
}

3.3.3 Minitalk での siginfo_t 活用

/*
 * si_pid を使用してクライアントを識別
 */
void minitalk_handler(int sig, siginfo_t *info, void *ctx)
{
    static pid_t current_client = 0;

    (void)ctx;  /* 未使用パラメータ */

    /*
     * info->si_pid で送信元を特定
     * これにより:
     *   1. クライアントの切り替えを検出
     *   2. ACK をクライアントに返送
     *   3. 複数クライアントの区別(ボーナス)
     */

    if (info->si_pid != current_client)
    {
        /* 新しいクライアントからの通信開始 */
        current_client = info->si_pid;
        /* 状態をリセット... */
    }

    /* ビット処理... */

    /* ACK を送信 */
    kill(info->si_pid, SIGUSR1);
}

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

3.4.1 シグナルセット(sigset_t)

シグナルセットは、複数のシグナルをグループ化するためのデータ型です:

#include <signal.h>

sigset_t set;

/* 操作関数 */
int sigemptyset(sigset_t *set);      /* 空に初期化 */
int sigfillset(sigset_t *set);       /* 全シグナルを追加 */
int sigaddset(sigset_t *set, int sig);   /* シグナルを追加 */
int sigdelset(sigset_t *set, int sig);   /* シグナルを削除 */
int sigismember(const sigset_t *set, int sig); /* 含まれるか確認 */

ビットマスクとしての内部表現:

sigset_t の概念的な構造:

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

例: SIGUSR1 と SIGUSR2 をセット
  set = 0b...0101000000000...
              ↑ ↑
            USR2 USR1

3.4.2 プロセスのシグナルマスク

各プロセスは「シグナルマスク」を持ち、どのシグナルがブロックされているかを管理します:

#include <signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

操作モード:

/*
 * SIG_BLOCK: 指定シグナルをマスクに追加
 */
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGUSR1);
sigprocmask(SIG_BLOCK, &set, NULL);
/* → SIGUSR1 は配送されなくなる(保留される) */


/*
 * SIG_UNBLOCK: 指定シグナルをマスクから削除
 */
sigprocmask(SIG_UNBLOCK, &set, NULL);
/* → SIGUSR1 が再び配送される */
/* → 保留されていた SIGUSR1 があればここで配送 */


/*
 * SIG_SETMASK: マスクを指定された値に置き換え
 */
sigset_t newmask, oldmask;
sigemptyset(&newmask);
sigaddset(&newmask, SIGUSR1);
sigaddset(&newmask, SIGUSR2);
sigprocmask(SIG_SETMASK, &newmask, &oldmask);
/* → マスクが newmask に置き換わる */
/* → 古いマスクは oldmask に保存される */

3.4.3 クリティカルセクションの保護

シグナルマスクを使用してクリティカルセクションを保護:

/*
 * Dijkstra のクリティカルセクション概念をシグナルに適用
 */

void critical_operation(void)
{
    sigset_t block_set, old_set;

    /* ブロックするシグナルを準備 */
    sigemptyset(&block_set);
    sigaddset(&block_set, SIGUSR1);
    sigaddset(&block_set, SIGUSR2);

    /* クリティカルセクション開始: シグナルをブロック */
    sigprocmask(SIG_BLOCK, &block_set, &old_set);

    /*
     * ===== クリティカルセクション =====
     * この間、SIGUSR1/SIGUSR2 は配送されない
     * 共有データを安全に操作可能
     */
    /* ... クリティカルな処理 ... */

    /* クリティカルセクション終了: マスクを復元 */
    sigprocmask(SIG_SETMASK, &old_set, NULL);

    /* ブロック中に到着したシグナルはここで配送される */
}

3.4.4 sa_mask の役割

sigactionsa_mask フィールドは、ハンドラ実行中に追加でブロックするシグナルを指定します:

void setup_handler(void)
{
    struct sigaction sa;

    sigemptyset(&sa.sa_mask);

    /*
     * ハンドラ実行中に追加でブロックするシグナル
     *
     * 注意: 処理中のシグナル自体は自動的にブロックされる
     *       (SA_NODEFER を指定しない限り)
     */
    sigaddset(&sa.sa_mask, SIGUSR1);
    sigaddset(&sa.sa_mask, SIGUSR2);

    sa.sa_sigaction = handler;
    sa.sa_flags = SA_SIGINFO | SA_RESTART;

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

/*
 * この設定により:
 *
 * SIGUSR1 ハンドラ実行中:
 *   - SIGUSR1 がブロック(自動)
 *   - SIGUSR2 がブロック(sa_mask で指定)
 *
 * → ハンドラの処理が確実に完了してから
 *   次のシグナルが処理される
 */

3.5 システムコールの中断と再開

3.5.1 EINTR 問題

シグナルはシステムコールを中断させる可能性があります:

/*
 * シグナルによるシステムコール中断
 */

ssize_t bytes = read(fd, buf, size);

if (bytes == -1)
{
    if (errno == EINTR)
    {
        /*
         * シグナルにより中断された
         * データは読み込まれていない
         *
         * 対処方法:
         *   1. 再試行する
         *   2. エラーとして処理する
         */
    }
}

「遅い」システムコールの例:

中断される可能性があるシステムコール:
  - read(), write() (端末、パイプ、ソケット)
  - open() (FIFO)
  - wait(), waitpid()
  - pause(), sigsuspend()
  - connect(), accept()
  - select(), poll()
  - msgsnd(), msgrcv()
  - fcntl() の一部

中断されないシステムコール:
  - ディスクI/O(通常のファイル)
  - メモリ操作
  - プロセス情報取得

3.5.2 SA_RESTART による自動再開

SA_RESTART フラグを設定すると、多くのシステムコールが自動的に再開されます:

/*
 * SA_RESTART の効果
 */

void setup_restartable_handler(void)
{
    struct sigaction sa;

    sigemptyset(&sa.sa_mask);
    sa.sa_handler = my_handler;
    sa.sa_flags = SA_RESTART;  /* 自動再開を有効化 */

    sigaction(SIGUSR1, &sa, NULL);
}

/*
 * SA_RESTART 有効時:
 *
 * プロセス:
 *   1. read(fd, buf, size) を呼び出し
 *   2. I/O 待ちでブロック
 *   3. SIGUSR1 到着
 *   4. ハンドラ実行
 *   5. read() が自動的に再開 ← SA_RESTART の効果
 *   6. データ受信後に戻る
 *
 * SA_RESTART なしの場合:
 *   5. read() が -1 を返し、errno = EINTR
 */

3.5.3 手動での再試行

SA_RESTART が使用できない場合の対処:

/*
 * EINTR を考慮した read() ラッパー
 */
ssize_t safe_read(int fd, void *buf, size_t count)
{
    ssize_t result;

    do {
        result = read(fd, buf, count);
    } while (result == -1 && errno == EINTR);

    return result;
}

/*
 * 部分読み取りも考慮した完全版
 */
ssize_t full_read(int fd, void *buf, size_t count)
{
    size_t total = 0;
    ssize_t n;

    while (total < count)
    {
        n = read(fd, (char *)buf + total, count - total);

        if (n == -1)
        {
            if (errno == EINTR)
                continue;  /* シグナル中断: 再試行 */
            return -1;     /* 他のエラー */
        }

        if (n == 0)
            break;  /* EOF */

        total += n;
    }

    return total;
}

3.6 再入可能性と async-signal-safety

3.6.1 再入可能性の概念

「再入可能」(reentrant)関数は、実行途中で再度呼び出されても正しく動作する関数です:

/*
 * 再入不可能な関数の例
 */
static int counter = 0;

int non_reentrant_increment(void)
{
    return ++counter;  /* 静的変数を変更 */
}

/*
 * 問題のシナリオ:
 *
 * 1. main() が non_reentrant_increment() を呼び出し
 * 2. counter を読み込み (counter = 5)
 * 3. シグナル到着、ハンドラに制御移動
 * 4. ハンドラが non_reentrant_increment() を呼び出し
 * 5. counter++ (counter = 6)
 * 6. ハンドラ終了、main() に戻る
 * 7. main() が counter++ を完了 (counter = 7)
 *
 * 期待: 2回インクリメントで +2
 * 実際: counter は元の値 +1 だけ増加(race condition)
 */


/*
 * 再入可能な関数の例
 */
int reentrant_add(int a, int b)
{
    return a + b;  /* ローカル変数と引数のみ使用 */
}

3.6.2 async-signal-safe 関数

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

/*
 * async-signal-safe な関数(POSIX.1-2017)
 *
 * シグナルハンドラ内で安全に呼び出せる関数
 */

/* I/O */
read(), write(), open(), close()
dup(), dup2()
lseek()

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

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

/* ファイルシステム */
access(), chmod(), chown()
link(), unlink(), rename()
stat(), fstat(), lstat()

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

async-signal-unsafe な関数(シグナルハンドラ内で使用禁止):

/*
 * 使用禁止の関数例
 */

printf(), fprintf()   /* 内部バッファとロックを使用 */
malloc(), free()      /* ヒープを操作 */
exit()                /* atexit() ハンドラを呼び出す */

fopen(), fclose()     /* stdio バッファリング */
fread(), fwrite()

pthread_* 関数        /* ロックを使用 */

/*
 * なぜ危険か:
 *
 * main() で printf("Hello") を実行中
 *   ↓
 * printf の内部ロックを獲得
 *   ↓
 * シグナル到着
 *   ↓
 * ハンドラで printf("Signal!") を呼び出す
 *   ↓
 * printf がロックを獲得しようとする
 *   ↓
 * デッドロック!(ロックは既に main() が保持)
 */

3.6.3 シグナルハンドラの安全な実装パターン

/*
 * パターン1: フラグを設定するだけ
 *
 * 最も安全なパターン
 */
volatile sig_atomic_t g_signal_received = 0;

void safe_handler(int sig)
{
    g_signal_received = 1;  /* フラグを設定するだけ */
}

int main(void)
{
    /* ... setup ... */

    while (1)
    {
        if (g_signal_received)
        {
            g_signal_received = 0;
            /* ここで安全に処理 */
            printf("Signal processed\n");  /* main では安全 */
        }
        /* ... other work ... */
    }
}


/*
 * パターン2: write() のみ使用
 *
 * シグナルハンドラ内で出力が必要な場合
 */
void handler_with_output(int sig)
{
    const char msg[] = "Signal received\n";
    write(STDOUT_FILENO, msg, sizeof(msg) - 1);  /* 安全 */
}


/*
 * パターン3: Minitalk スタイル
 *
 * ビット処理と write() のみ
 */
void minitalk_safe_handler(int sig, siginfo_t *info, void *ctx)
{
    static unsigned char ch = 0;
    static int bits = 0;

    (void)ctx;

    /* ビット操作(ローカル/静的変数のみ) */
    ch <<= 1;
    if (sig == SIGUSR2)
        ch |= 1;
    bits++;

    if (bits == 8)
    {
        write(1, &ch, 1);  /* async-signal-safe */
        kill(info->si_pid, SIGUSR1);  /* async-signal-safe */
        ch = 0;
        bits = 0;
    }
}

3.7 Minitalk サーバーの実装

3.7.1 堅牢なサーバー設計

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

/*
 * グローバル状態
 *
 * 42の制約: グローバル変数は1つまで
 * 構造体でまとめることで遵守
 */
typedef struct s_server
{
    unsigned char   current_char;   /* 構築中の文字 */
    int             bit_count;      /* 受信ビット数 */
    pid_t           client_pid;     /* 現在のクライアント */
}   t_server;

static t_server g_server = {0, 0, 0};

/*
 * シグナルハンドラ
 *
 * async-signal-safe な関数のみ使用:
 *   - write()
 *   - kill()
 */
void signal_handler(int signum, siginfo_t *info, void *context)
{
    (void)context;

    /* クライアント切り替えの検出 */
    if (g_server.client_pid != info->si_pid)
    {
        g_server.client_pid = info->si_pid;
        g_server.current_char = 0;
        g_server.bit_count = 0;
    }

    /* ビット処理 */
    g_server.current_char <<= 1;
    if (signum == SIGUSR2)
        g_server.current_char |= 1;
    g_server.bit_count++;

    /* 8ビット完了 */
    if (g_server.bit_count == 8)
    {
        if (g_server.current_char == '\0')
        {
            write(1, "\n", 1);
            kill(g_server.client_pid, SIGUSR2);  /* 完了通知 */
        }
        else
        {
            write(1, &g_server.current_char, 1);
            kill(g_server.client_pid, SIGUSR1);  /* ACK */
        }

        g_server.current_char = 0;
        g_server.bit_count = 0;
    }
}

/*
 * シグナルハンドラの設定
 */
void setup_signals(void)
{
    struct sigaction sa;

    /* 構造体を初期化 */
    sigemptyset(&sa.sa_mask);

    /* ハンドラ実行中に両シグナルをブロック */
    sigaddset(&sa.sa_mask, SIGUSR1);
    sigaddset(&sa.sa_mask, SIGUSR2);

    /* ハンドラと設定 */
    sa.sa_sigaction = signal_handler;
    sa.sa_flags = SA_SIGINFO | SA_RESTART;

    /* エラーチェック付きで登録 */
    if (sigaction(SIGUSR1, &sa, NULL) == -1)
    {
        write(2, "Error: sigaction failed\n", 24);
        _exit(1);  /* async-signal-safe な終了 */
    }

    if (sigaction(SIGUSR2, &sa, NULL) == -1)
    {
        write(2, "Error: sigaction failed\n", 24);
        _exit(1);
    }
}

/*
 * PID 表示用ヘルパー
 */
void ft_putnbr(int n)
{
    char c;

    if (n >= 10)
        ft_putnbr(n / 10);
    c = (n % 10) + '0';
    write(1, &c, 1);
}

/*
 * メイン関数
 */
int main(void)
{
    pid_t pid;

    pid = getpid();

    write(1, "Server PID: ", 12);
    ft_putnbr(pid);
    write(1, "\n", 1);

    setup_signals();

    write(1, "Waiting for messages...\n", 24);

    /* 無限ループでシグナルを待つ */
    while (1)
        pause();

    return (0);
}

3.7.2 エラー回復機能

/*
 * タイムアウト検出付きハンドラ
 *
 * SIGALRM を使用してクライアントの応答タイムアウトを検出
 */
void setup_timeout_handler(void)
{
    struct sigaction sa;

    sigemptyset(&sa.sa_mask);
    sa.sa_handler = timeout_handler;
    sa.sa_flags = SA_RESTART;

    sigaction(SIGALRM, &sa, NULL);
}

void timeout_handler(int sig)
{
    (void)sig;

    if (g_server.bit_count > 0)
    {
        /* 不完全なデータを破棄 */
        const char msg[] = "\n[Timeout: incomplete data discarded]\n";
        write(2, msg, sizeof(msg) - 1);

        g_server.current_char = 0;
        g_server.bit_count = 0;
        g_server.client_pid = 0;
    }
}

void signal_handler_with_timeout(int signum, siginfo_t *info, void *ctx)
{
    (void)ctx;

    /* アラームをリセット(次のシグナルまで5秒待つ) */
    alarm(5);

    /* 通常の処理... */
    /* ... */
}

3.8 Minitalk クライアントの実装

3.8.1 ACK 付きクライアント

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

static volatile sig_atomic_t g_ack = 0;

void ack_handler(int sig)
{
    (void)sig;
    g_ack = 1;
}

/*
 * 1ビット送信(ACK待機付き)
 */
int send_bit(pid_t server_pid, int bit)
{
    int timeout;

    g_ack = 0;

    if (kill(server_pid, bit ? SIGUSR2 : SIGUSR1) == -1)
    {
        write(2, "Error: kill failed\n", 19);
        return (0);
    }

    /* ACK を待つ */
    timeout = 10000;  /* 最大100ms */
    while (!g_ack && timeout > 0)
    {
        usleep(10);
        timeout--;
    }

    if (!g_ack)
    {
        write(2, "Error: ACK timeout\n", 19);
        return (0);
    }

    return (1);
}

/*
 * 1文字送信
 */
int send_char(pid_t server_pid, unsigned char c)
{
    int bit;

    bit = 7;
    while (bit >= 0)
    {
        if (!send_bit(server_pid, (c >> bit) & 1))
            return (0);
        bit--;
    }

    return (1);
}

/*
 * 文字列送信
 */
int send_string(pid_t server_pid, const char *str)
{
    while (*str)
    {
        if (!send_char(server_pid, *str))
            return (0);
        str++;
    }

    /* 終端の NUL */
    return send_char(server_pid, '\0');
}

/*
 * 文字列を数値に変換
 */
pid_t ft_atoi(const char *str)
{
    pid_t result = 0;

    while (*str >= '0' && *str <= '9')
    {
        result = result * 10 + (*str - '0');
        str++;
    }

    return result;
}

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

    if (argc != 3)
    {
        write(2, "Usage: ./client <server_pid> <message>\n", 39);
        return (1);
    }

    server_pid = ft_atoi(argv[1]);
    if (server_pid <= 0)
    {
        write(2, "Error: Invalid PID\n", 19);
        return (1);
    }

    /* ACK ハンドラを設定 */
    sigemptyset(&sa.sa_mask);
    sa.sa_handler = ack_handler;
    sa.sa_flags = SA_RESTART;
    sigaction(SIGUSR1, &sa, NULL);
    sigaction(SIGUSR2, &sa, NULL);

    if (send_string(server_pid, argv[2]))
        write(1, "Message sent!\n", 14);
    else
        write(2, "Error: Failed to send\n", 22);

    return (0);
}

3.9 まとめ

この章では、POSIX シグナル標準と sigaction() について学びました:

学んだこと

  • POSIX 標準化の歴史
- UNIX の分裂と互換性問題 - IEEE による標準化(1988年) - signal() から sigaction() への移行

  • sigaction() の設計
- 一貫性、原子性、制御性 - struct sigaction の各フィールド - sa_flags の詳細

  • siginfo_t 構造体
- 送信元 PID の取得 - si_code によるシグナル発生理由 - Minitalk での活用

  • シグナルマスク
- sigset_t の操作 - sigprocmask() による制御 - クリティカルセクションの保護

  • SA_RESTART と EINTR
- システムコールの中断問題 - 自動再開と手動再試行

  • async-signal-safety
- 再入可能性の概念 - 安全な関数と禁止関数 - 安全なハンドラの実装パターン

次章の予告

第4章では、ビット操作と文字エンコーディング について学びます:

  • ビット演算の詳細
  • 文字の送受信アルゴリズム
  • Unicode と UTF-8

---

参考文献:

  • IEEE Std 1003.1-2017. POSIX.1-2017 (IEEE Standard for Information Technology). IEEE.
  • Stevens, W. R., & Rago, S. A. (2013). Advanced Programming in the UNIX Environment (3rd ed.). Addison-Wesley.
  • Kerrisk, M. (2010). The Linux Programming Interface. No Starch Press.
  • Bach, M. J. (1986). The Design of the UNIX Operating System. Prentice Hall.