OS基礎 + プロセス・スレッド
OS基礎 + プロセス・スレッド
🎯 このlessonを読んだ後に
このlessonを読み終えると、以下の3つを自信を持って説明できるようになります。
- ▸✅ OSの4大役割 (プロセス・メモリ・ファイルシステム・I/O)
- ▸✅ User Mode vs Kernel Mode + syscall
- ▸✅ Node.jsがなぜシングルスレッドなのに速いのか
学習目標をチェックリストとして持ち、すべてに答えられるようになったらlessonを閉じてください。
OSがする4つのこと
一言で: オペレーティングシステム (OS) = ハードウェアの上でプロセス・メモリ・ファイル・I/Oを抽象化するソフトウェア層。
OSの4大責務:
ブート → カカオトーク起動の流れ (6段階):
1. 電源ON — BIOS/UEFI → ブートローダー → カーネルロード
2. カーネル初期化 — メモリマッピング・ドライバロード・スケジューラ起動
3. initプロセス — PID 1、すべてのユーザープロセスの親 (systemd / launchd)
4. ログインシェル — bash/zsh 起動、環境変数ロード
5. アプリ起動 — fork() + exec("/Applications/KakaoTalk.app/...") → 新しいプロセス
6. イベントループ — キーボード・ネットワーク入力の待機・処理
> 💡 カカオトークを1回起動 = OSが上記6段階を見えないところで実行。毎回0.5秒以内に完了。
プロセス vs スレッド — 並行性の2つのモデル
いつ何を使うか:
- ▸プロセス分離: 安定性優先 (Chromeのタブごとのプロセス)、セキュリティ分離 (Dockerコンテナ)、他言語との統合
- ▸スレッド使用: 軽量な並行性 (Webサーバーのリクエストごとのスレッド)、共有データ処理 (GUIイベントループ)
- ▸現代のトレンド: 仮想スレッド (Java 21)・ゴルーチン (Go)・async/await (Python、Node) — スレッドよりさらに軽い軽量並行性
よくある間違い:
- ▸❌ スレッドを1000個生成 → コンテキストスイッチコストが爆発
- ▸❌ ロックなしで共有データを変更 → race condition
- ▸✅ スレッドプール (ThreadPoolExecutor) + ロック または async/await を使用
コンテキストスイッチング — 並行性の*本当のコスト*
コンテキストスイッチングの6段階 (CPUがプロセスA → Bに切り替える):
1. 割り込み — タイマー・I/O完了・システムコール
2. Aの状態保存 — レジスタ・PC・スタックポインタをPCB (Process Control Block) に
3. スケジューラ呼び出し — 次に実行するプロセスを選択 (CFS・O(1)・リアルタイム)
4. BのPCBロード — レジスタ・MMUマッピングを復元
5. キャッシュ無効化 — L1/L2キャッシュ・TLB (Translation Lookaside Buffer) の一部をフラッシュ
6. B実行 — 停止した箇所から再開
コスト:
オーバーヘッドの罠:
- ▸❌ スレッドが多すぎる (1000+) → CPUが実際の作業よりスイッチングに多くの時間を費やす
- ▸❌ 非同期コードなのにCPUバウンドな処理 → メインスレッドをブロック
- ▸✅ CPUコア数に近いスレッド数 (おおよそN + 1) を推奨
> 💡 なぜ非同期が速いのか? = コンテキストスイッチングなしで1スレッドが複数タスクを処理 (イベントループ)。
システムコール・割り込み・コンテキストスイッチング — 1ページで
ユーザー空間 vs カーネル空間
OSのメモリは2つの領域に分かれています:
- ▸ユーザー空間 (User Space) — 私たちのアプリが実行される場所。権限が制限されている。メモリ・ディスク・ネットワークへの直接アクセス不可。
- ▸カーネル空間 (Kernel Space) — OS自体が存在する場所。すべてのハードウェア権限を持つ。
ユーザーアプリがファイルを開くには — ディスクに直接アクセスできません。必ずOSにリクエストしなければなりません。このリクエストがシステムコールです。
システムコール (System Call)
open / read / write / close / fork / exec — Linuxでは約300個。すべてのI/O・プロセス生成がsyscall。
高水準言語 (Python, Java) のすべてのファイル/ネットワーク呼び出しは内部的にsyscallを呼び出します。
syscallのコスト
ユーザー空間 → カーネル空間の切り替え自体が高コストです (マイクロ秒単位)。そのため:
- ▸バッファリング — 毎回syscallせず、まとめて一度に処理。
BufferedReader、console.logの内部バッファ。 - ▸非同期I/O — Node.js、async/await — syscall待機中に他の処理を実行。
割り込み vs ポーリング
ポーリング (Polling) — ひたすら確認する
CPU時間の無駄遣い。ビジーウェイト。
割り込み (Interrupt) — 通知を受け取る
ハードウェアが「準備完了!」というシグナルを送ると、CPUが即座に処理します:
- ▸キーボード入力 — キーを押した瞬間にIRQが発生 → カーネルがイベントに変換 → アプリに配送
- ▸ネットワークパケット到着 — NICが割り込みを発生 → カーネルがバッファに保存 → アプリを起こす
- ▸タイマー満了 —
setTimeoutの基盤
「ポーリングは非効率、割り込みが標準」 — 現代OSのほぼすべてのI/Oは割り込みベース。
コンテキストスイッチング (Context Switching)
CPUがプロセス/スレッドを切り替えるとき — 現在の状態 (レジスタ・PC・メモリマッピング) を保存し、次のタスクの状態を復元します。
コスト
- ▸プロセススイッチング — メモリマッピングも切り替える必要がある。高コスト (~数μs)。
- ▸スレッドスイッチング — 同じメモリを共有。プロセスより5〜10倍軽い。
- ▸関数呼び出し — 単純なスタックプッシュ。ナノ秒単位。
そのため — マルチスレッドはマルチプロセスより高速です。
なぜNode.jsはシングルスレッドなのに速いのか
Node.jsの「シングルスレッド + イベントループ」:
核心アイデア:
1. I/Oが99%の時間を占める (DB・API・ディスク)
2. I/O待機中にメインスレッドが他のリクエストを処理
3. コンテキストスイッチングコストがほぼゼロ — スレッドが1つだけ
Apacheのような「リクエストごとのスレッド」モデル: 1万リクエスト = 1万スレッド = コンテキストスイッチング爆発 + メモリ爆発。
Nodeモデル: 1万リクエスト = 1メインスレッド + 4〜8ワーカースレッド + イベントキュー。比較にならない効率。
まとめ — バイブコーディングとの接続
- ▸fetch / ファイル読み込み = syscall → 高コスト → 最小化 (バッチ処理・キャッシング)
- ▸Node.js・async/await = イベント駆動 → I/Oの多いサーバーに強い
- ▸CPU負荷の高い処理はWorker Thread → メインイベントループをブロックしないこと
面接で「Node.jsはなぜ速いのか」と聞かれたら、このページの内容をそのまま答えれば合格です。
🤖 AIにこう依頼してみよう
このlessonの概念を知っていると、AIに具体的に指示できます。漠然とした「直して」ではなく、語彙を持ったリクエスト — それがトークン節約の出発点です。
- ▸「この処理のsyscallトレースをstrace (Linux) で実行するコマンドを教えて」
- ▸「このコードがユーザー空間 / カーネル空間のどちらでコストが高いか診断して」
なぜこれがトークンを減らすのか
概念を知らないと、AIの回答を受け取っても「それは何ですか?」と再度質問しなければなりません。その「再質問」がトークンを消費します。概念を一度習得すれば、会話が一度で完結します。