第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 の誕生
SIGUSR1 と SIGUSR2 は、UNIX System III(1982年)で追加された「ユーザー定義シグナル」です:
/* UNIX System III (1982) で追加 */
#define SIGUSR1 10 /* User-defined signal 1 */
#define SIGUSR2 12 /* User-defined signal 2 */
これらのシグナルには以下の特徴があります:
> "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
シグナルハンドラとメインコード間でデータを共有する場合、volatile と sig_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)
- UNIXシグナルの誕生(1971-1979年)
- 非同期処理の理論
- シグナル配送メカニズム
- シグナルハンドラの制約
- Minitalkプロジェクトの概要
次章の予告
第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.