ジュニアエンジニア向け コンピュータサイエンス入門シリーズ(全8回)
第4回:OSの仕事:「ファイルを開く」は何をしているのか

「ファイルを開く」は何をしているのか:OSという管理人の4つの仕事【第4回】

20個のアプリを同時に動かしても、互いのメモリを壊しません。16GBのマシンで20GBのデータを扱っているように見えます。なぜそんなことが成り立つのか。この記事を読み終えると、OSが「プロセス」「仮想メモリ」「ファイル」「権限」という4つの仕組みで何を解決しているのかを説明でき、権限エラーやファイルディスクリプタの枯渇を自分で切り分けられるようになります。ジュニアエンジニア向けコンピュータサイエンス入門シリーズ(全8回)の第4回です。

🎯 テーマの主役:「OSという管理人」——限られた資源を、多数の住人に配る

今回の主役はオペレーティングシステム(OS)です。一言で言えば、ハードウェアという限られた資源を、多数のプログラムに公平に配り、互いに壊し合わないように守る管理者です。

第1回で作った地図を思い出してください。コンピュータは5階建てのビルで、2階がOSでした。あのとき「2階は資源を配る階。ここで断られると4階では何もできない」と書きました。今回の記事は、その2階の管理室の中に入って、管理人が何をしているのかを1日観察する回です。

日常の例えで言うなら、OSはビルの管理人(コンシェルジュ)です。ビルには電気・水道・共用の倉庫・入館証という限られた資源があり、多数のテナントがそれを使いたがっています。管理人は次の仕事をしています。第一に、入居者を部屋に割り当てる(プロセス)。第二に、各テナントに「自分の部屋の見取り図」を配り、実際にどこにあるかは教えない(仮想メモリ)。第三に、共用の倉庫の鍵を管理し、番号札で貸し出す(ファイル)。第四に、誰がどの部屋に入れるかを決める(権限)。

この4つの仕事を理解すると、次の4つができるようになります。第一に、プロセスが何であり、なぜ突然消えるのか(Killed されるのか)を説明できること。第二に、なぜメモリ不足で落ちるのか、なぜ20GBを扱えるように見えるのかを説明できること。第三に、ファイルディスクリプタの枯渇というエラーの意味が分かること。第四に、権限エラーの正体と「最小権限」という考え方を理解することです。

OSをビルの管理人にたとえた概念イラスト ビルの管理人が、入居者に部屋を割り当て、各自に自分の見取り図だけを渡し、共用倉庫の番号札を貸し出し、入館証で入れる部屋を決めている様子を描く。 OSは「限られた資源を、多数の住人に配って守る」管理者 第1回で「2階=資源を配る階」と呼んだ場所の中身 受付カウンター =システムコール 管理人 =カーネル 自分では入れない場所は ここでお願いする CPU・メモリ・ディスクを持つ ① 部屋を割り当てる =プロセス それぞれ別の部屋。互いに覗けない ② 見取り図を配る =仮想メモリ 1号室の見取り図 「全部1階から」 と書いてある 2号室の見取り図 こちらも 「全部1階から」 実際の場所は管理人だけが知っている ③ 番号札で貸す =ファイル 札 3 → 棚の資料 札 4 → 別の資料 ④ 入れる部屋を決める =権限 持っている鍵の束 r w x × 読める・書ける・入れない 管理人が介入する2つの場面 A テナントが「倉庫を開けて」と依頼する 依頼しないと何も借りられない(システムコール) B 持っていない鍵の部屋を開けようとした 管理人に止められる(=権限エラー)

OSの4つの仕事を、対応する仕組みとエラーの形で整理しておきます。エラーは「管理人が断った理由」を教えてくれるという点が大事です。

仕事仕組み何を解決しているか断られたときに出るエラー
① 実行の単位を作るプロセス複数のプログラムが同時に動ける。1つが暴走しても他を壊さないKilled(強制終了)、ゾンビプロセスの増加、応答なし
② メモリを配る仮想メモリ各プロセスが独立した記憶空間を持てる。物理量より多く見せられるCannot allocate memory、Segmentation fault、OOM
③ 永続データを扱うファイル・ファイルディスクリプタ電源を切っても残る。番号札で安全に開閉できるENOENT、EMFILE、EISDIR、ENOSPC
④ 誰が何をしてよいか決めるユーザー・権限利用者ごとにできることを分けられる。事故と攻撃を防ぐEACCES(permission denied)

😓 動機:OSを「見えないもの」として扱ってしまう

現代の開発では、OSを直接意識する場面が減りました。コンテナが環境を包み、クラウドがマシンを抽象化し、フレームワークがファイル操作を隠します。それは便利なことです。しかし抽象が漏れた瞬間に、何も言えなくなるという副作用があります。

よくある場面を4つ挙げます。ひとつ目は、Killed とだけ表示されてプロセスが消える。エラーメッセージもスタックトレースもなく、ただ消える。ふたつ目は、「Too many open files」というエラーが出て、何を数えればいいのか分からない。みっつ目は、ローカルでは動くのにコンテナでは権限エラーになる。よっつ目は、ログに書いたはずのデータが消えている。プロセスが落ちた直後のログが残っていない。

これらはすべて、OSの4つの仕事のどれかが関係しています。そして厄介なことに、この4つは「動いているとき」は完全に見えません。テストも通るし、レビューも通る。壊れるのは本番の、しかも負荷がかかった瞬間だけです。

🧪 仮説:OSの仕事は「配分」と「保護」の2つに還元できる

仮説を立てます。OSの4つの仕事は、すべて「限られた資源の配分」と「住人どうしの保護」という2つの目的に還元できる。

この仮説を支持する観察が3つあります。第一に、プロセスという仕組みは「CPU時間の配分」と「互いのメモリを触らせない保護」を同時に実現しています。第二に、仮想メモリは「物理メモリという有限資源の配分」と「他プロセスのデータを読めない保護」を同時に実現しています。第三に、権限は「資源へのアクセスの配分」と「事故・攻撃からの保護」を同時に実現しています。

この仮説が正しければ、OSの挙動は「何の資源が足りないのか」「誰から誰を守ろうとしているのか」という2つの問いで読み解けます。Killed は「メモリという資源の配分が上限に達した」、EACCES は「保護のために止められた」。エラーコードは、この2つのどちらかを教えてくれているのです。

🔬 検証①:プロセス——レシピと、調理中の料理

まず、いちばん基本的な単位から見ていきます。プロセスです。

ここで混同されやすいのが、プログラムプロセスの違いです。料理に例えると分かりやすいでしょう。プログラムはレシピ本であり、プロセスはそのレシピで実際に調理している状態です。レシピ本は何冊あっても誰も困りませんが、調理中の鍋は場所と火力を占有します。同じレシピから複数の料理を同時に作ることもできます。これが「同じプログラムから複数のプロセスが生まれる」という状況です。

プログラムはレシピ、プロセスは調理中の料理であることを示す概念イラスト 1冊のレシピ本から、コンロの上で別々に調理されている3つの鍋が生まれる様子を描き、プログラムとプロセスの違いを示す図。 レシピ本(プログラム)は1冊でも、調理中の鍋(プロセス)は同時に3つ置ける レシピ本 ディスク上に 置いてあるだけ =プログラム 誰も困らない 実行 プロセス 1 PID 4821 状態:実行中 =プロセス メモリとCPUを占有している プロセス 2 PID 4822 状態:待機中(I/O待ち) 同じプログラムから 別のプロセスが生まれる プロセス 3 PID 4823 状態:停止(一時中断) それぞれが 独立した部屋に住んでいる

ここで重要な性質が3つあります。第一に、プロセスは独立した記憶空間を持つこと。だから隣のプロセスが何をしているか分かりませんし、壊すこともできません。第二に、プロセスには固有の番号(PID)が振られること。Killed されたプロセスを調べるときに使う番号です。第三に、プロセスには状態があること。実行中だけでなく、待機中や停止中があります。

状態何をしているか日常のたとえよくある原因
実行中(Running)CPUを使って計算している調理している最中
待機中(Sleeping)I/O・タイマー・ロックの完了を待っている材料が届くのを待っているディスク・ネットワーク・DB応答待ち
停止中(Stopped)シグナルで一時停止させられている「ちょっと待って」と言われた状態デバッガによる中断、SIGSTOP
ゾンビ(Zombie)終了したが親が結果を受け取っていない料理は完成したが誰も取りに来ない親プロセスが待機処理をしていないバグ

そして、いちばん驚くべき性質は同時実行の作り方です。1つのCPUコアで何百ものプロセスが同時に動いているように見えるのは、OSがごく短い時間で切り替えているからです。この切り替えをコンテキストスイッチと呼びます。切り替えのたびに、今の作業状態(レジスタの中身など)を保存し、次に動かすプロセスの状態を復元する必要があります。第3回で扱ったメモリの階段を思い出してください。この保存と復元は、キャッシュを汚す作業です。だからプロセスを増やしすぎると、切り替えのコストだけで性能が落ちます。

🔬 検証②:仮想メモリ——全員に「自分の部屋」を配る嘘

次は、OSのいちばん巧妙な仕事です。仮想メモリです。

第3回でスタックとヒープを扱いましたが、あれは1つのプロセスの中の話でした。今回はその外側の話です。複数のプロセスが、それぞれ自分専用のアドレス空間を持っているように見えるのはなぜでしょうか。

答えは、OSが「嘘の住所」を配っているからです。各プロセスは「0番地から始まる自分の記憶空間」を見ています。プロセスAもプロセスBも、同じ「0番地」にアクセスしているつもりです。しかし実際の物理メモリ上では、別々の場所に置かれています。その対応表を持っているのがOS(とCPUのMMU)です。

各プロセスが見ている仮想アドレスと実際の物理メモリの対応を示す図 プロセスAとBがどちらも0番地から始まる住所を見ているが、対応表によって物理メモリの別々の場所に割り当てられている様子を示す図。 2つのプロセスが同じ「0番地」を使っているのに、ぶつからない 住所を嘘にしているから、壊しようがない プロセスAが見ている住所 0番地 〜 999番地 1000番地 〜 1999番地 2000番地 〜 「自分は0番地から住んでいる」 プロセスBが見ている住所 0番地 〜 999番地 1000番地 〜 1999番地 2000番地 〜 こちらも「0番地から住んでいる」 対応表 ページテーブル Aの0番地 → 物理 5000番地 Bの0番地 → 物理 9000番地 実際の物理メモリ OSの領域 A のデータ(5000番地) 使われていない B のデータ(9000番地) 空き 飛び飛びでよい 住所変換はハードウェアが担当する CPUの中の MMU(メモリ管理ユニット)が、毎回のアクセスで自動変換する 変換表の一部は TLB にキャッシュされ、変換自体を速くしている プログラムは「嘘の住所」を意識せずに書ける

この仕組みの利点は、安全性だけではありません。物理メモリより大きな空間を使えるように見せられます。使われていないページはディスクに退避させ、必要になったら戻せるからです。これが「16GBのマシンで20GBを扱える」ように見える理由です。ただし、実際に使っている量が物理メモリを超えると、退避と復元が繰り返されて極端に遅くなります。この状態が、第3回の用語でいう断崖を何度もまたぐ状態です。

用語意味日常のたとえ
仮想アドレスプロセスが見ている「嘘の住所」自分の部屋につけた通し番号
物理アドレス実際のメモリ上の位置建物の本当の住所
ページ住所を区切る単位(Linuxのx86_64では通常4KB)部屋を区切る間仕切り単位
ページテーブル仮想と物理の対応表管理人だけが持つ台帳
TLB変換結果のキャッシュ。変換自体を速くするよく使う対応を書いた付箋
ページフォールト必要なページが物理メモリに無い状態倉庫から取り寄せが発生する
セグメンテーション違反対応表に無い住所にアクセスした存在しない部屋を開けようとした

ここで、第1回で扱った「エラーの階切り分け」が効いてきます。セグメンテーション違反は、あなたのコードのバグ(4階)ではなく、OSが止めた結果(2階)です。ただし止められた原因は、多くの場合4階にあります。配列の範囲外アクセス、解放済みメモリの参照、ポインタの誤り。OSは正しく仕事をした結果として、あなたのバグを暴いたわけです。

🔬 検証③:ファイル——「開く」は番号札をもらう行為

3つ目の仕事はファイルです。ここで最初に考えたいのは、「ファイルを開く」とは何をしているのかです。

多くの人は「開く=内容を読み込む」と思っています。しかし実際には違います。開くとは、OSに「このファイルを使いたい」と申請して、番号札を受け取る行為です。この番号札をファイルディスクリプタと呼びます。番号札をもらった後、読み書きは番号札を指定して依頼します。

ファイルを開くと番号札を受け取り番号で操作する仕組みを示す概念イラスト プログラムが管理人にファイルを開けるよう依頼し、番号札を受け取り、以後はその番号だけを使って読み書きする様子を描く図。 「開く」は内容を読むことではなく、番号札を受け取ること あなたのプログラム open("report.txt") read(3, ...) 以後は「3」で話す 依頼 管理人の台帳 札 0 → キーボード 札 1 → 画面(出力) 札 2 → 画面(エラー) 札 3 → report.txt 起動時に0・1・2は配られている 対応 ディスク上のファイル report.txt 中身はここ 別のファイル まだ開いていない 番号札には上限がある プロセスごとに「同時に開ける数」が決まっている 上限を超えると EMFILE(Too many open files)

ここで2つの実務的なポイントが出ます。第一に、番号札は有限だということ。プロセスごとに同時に開ける数の上限があります。開いたまま閉じ忘れると、やがて EMFILE(Too many open files)で失敗します。第1回の切り分け表で「EMFILE→2F」と書いたのは、これがOS側の資源の話だからです。「開きっぱなしにしない」は、OSの資源を守る行為なのです。

第二に、番号札の0・1・2は最初から配られていること。0が標準入力、1が標準出力、2が標準エラー出力です。これは「最初から3枚の札を持って生まれる」という意味です。だからファイルを1つ開くと番号は3から始まります。パイプやリダイレクトが |> という記号1つで実現できるのは、「番号札の向き先を付け替えるだけ」だからです。シェルは特別な魔法をしているのではなく、OSの仕組みをそのまま使っています。

番号名前既定の向き先付け替えるとどうなるか
0標準入力(stdin)キーボードファイルから読ませる(< file
1標準出力(stdout)画面ファイルへ書かせる(> file)、次のコマンドへ渡す(|
2標準エラー出力(stderr)画面エラーだけ別ファイルへ分ける(2> error.log
3以降プログラムが開いたファイル使ったら閉じる。開きっぱなしはEMFILEの原因

そして、ファイルを扱う上で見落とされやすい性質がもう1つあります。書き込みはすぐにはディスクに届かないということです。OSは性能のために書き込みをメモリ上に溜めてから、まとめてディスクへ書きます(これをバッファリングと呼びます)。第3回で見たとおり、ディスクはメモリより桁違いに遅いので、これは合理的な設計です。しかし、その間に電源が落ちると、書いたはずのデータが消えます。確実に残したいときは、OSに「今すぐディスクに書いて」と明示的に依頼する必要があります。この依頼が fsync です。「書いたはずなのに残っていない」というトラブルの多くは、この仕組みを知らないことで起きます

🔬 検証④:権限——「できること」を鍵で分ける

最後の仕事は権限です。これは「誰が何をしてよいか」を決める仕組みです。

基本はシンプルです。すべてのファイルには所有者がいて、誰が何をできるかが決まっている。できることは3つに分かれます。読む(r)・書く(w)・実行する(x)。そして「誰」も3つに分かれます。所有者・グループ・その他の人。3×3の組み合わせなので、9ビットで表現できます。これが rwxr-xr-x のような表記であり、755 のような8進数表記です。

権限を鍵束にたとえた概念イラストと9ビットの対応 所有者・グループ・その他の3つの区分それぞれに読む・書く・実行の3つの鍵があり、その組み合わせが755のような数字になることを示す図。 「誰が」「何を」できるかを、鍵の束で表している 3つの区分 × 3種類の操作 = 9ビット 所有者(本人) r w x 読める・書ける・実行できる 4+2+1 = 7 グループ(同僚) r w 読める・書ける・実行できない 4+2 = 6 その他(第三者) r 読めるだけ 4 = 4 7・6・4 を並べた「764」がこのファイルの権限。表記は rwxrw-r-- 読み書きはできるが実行はできない。第三者には読ませる、という意味になる
8進数記号表記意味よく使う場面
644rw-r--r--本人は読み書き可。他は読めるだけ設定ファイル、画像、データファイル
600rw-------本人だけが読み書き可秘密鍵、認証情報、個人データ
755rwxr-xr-x本人は全部可。他は実行と読み取り実行ファイル、ディレクトリ
700rwx------本人だけが全部可他人に見せたくないディレクトリ
777rwxrwxrwx全員が何でもできる原則として使わない

ここで、権限の設計思想について1段深く掘ります。なぜ「実行する(x)」が別扱いなのか。考えるヒントは、「読めること」と「実行できること」は違うという点です。スクリプトの中身を読めることと、それを実行してよいことは別の判断です。読み取り権限だけを配れば、内容を確認させつつ勝手に走らせることは防げます。権限を分けるとは、判断を分けることなのです。

もう1つ掘りたいのが、root(管理者)が持つ力の性質です。rootはすべての権限を無視できます。便利ですが、これは「事故が起きたときに止める仕組みが無い」ということを意味します。だから実務では 最小権限の原則(必要最低限の権限だけを与える)が重視されます。sudo が「毎回パスワードを求める」のは面倒にするためではなく、危険な操作に一拍置くための設計です。この「一拍」が事故を減らします。

📊 結果:OSを意識すると、何が変わるか

ここまでの内容を、実際の切り分けの形にまとめます。エラーコードは「管理人が何を断ったか」を教えてくれるので、読む場所が絞れます。

症状OSのどの仕事か確認するものよくある原因
プロセスが Killed で消えるメモリの配分メモリ使用量の推移、コンテナのメモリ上限メモリの使いすぎ。OSが強制終了させた
急に全部が遅くなるメモリの配分ディスクの読み書き量(スワップの発生)物理メモリを超えて退避と復元が繰り返されている
EMFILE / Too many open filesファイルの番号札開いたままの接続・ファイル、上限設定閉じ忘れ。プールの最大数の設定ミス
EACCES / permission denied権限ファイルの所有者、実行ユーザー書き込み先の権限不足。コンテナ内のユーザー違い
ENOENTファイル作業ディレクトリとパスの基準相対パスの解釈が実行環境で違う
ログが途中で消えているファイル(バッファ)書き込み後にディスクへ同期しているかOSのバッファに溜まったままプロセスが落ちた
コンテナだけ挙動が違うプロセス・権限・メモリコンテナのユーザー、上限、ファイルシステム隔離の設定がローカル環境と違う

特に1行目と7行目が、コンテナを使う現代で最も出会うものです。どちらも「OSのどの資源がどう制限されているか」という問いに還元できます。

💭 考察:OSは「嘘をつく」ことで成り立っている

ここまでの4つの仕事を振り返ると、共通する構造が見えてきます。OSは、すべて「嘘」をつくことで成り立っています

プロセスは「自分がマシンを独占している」という嘘です。仮想メモリは「自分が0番地から広大なメモリを持っている」という嘘です。ファイルは「連続した1つのデータの塊である」という嘘です(実際にはディスク上に飛び飛びに置かれています)。権限は「みんなが同じように振る舞える」という前提を、裏側で条件分岐させている仕組みです。

この嘘は、便利な嘘です。嘘がなければ、プログラマは「他のプロセスがどこを使っているか」「データがディスクのどのセクタにあるか」を常に気にしながらコードを書かねばなりません。嘘のおかげで、私たちは自分の部屋だけを見て仕事ができます

ただし第1回で確認したとおり、抽象は漏れます。そしてOSの嘘は、資源が足りなくなった瞬間に漏れます。メモリが足りなければ Killed、番号札が足りなければ EMFILE、鍵が足りなければ EACCESOSの嘘が漏れる音は、エラーコードという形で聞こえてくるのです。

ここから実務的な指針が1つ出ます。OSのエラーが出たら「何の資源が足りないのか」を問う。原因を探す前に、まずこの問いを立てるだけで、見る場所が3つほどに絞られます。「メモリか、番号札か、権限か、パスか」。資源の種類が分かれば、対策の方向も決まります。メモリなら使用量を減らすか上限を上げる。番号札なら閉じ忘れを直す。権限なら所有者を見直す。

そして、もう1つ深い見方があります。OSの設計は「すべてを信用しない」という原則に貫かれているということです。プロセスは他のプロセスを信用しません。ユーザー空間のプログラムはカーネルを信用しません(だからシステムコールという境界を通す必要があります)。ファイルの読み書きは毎回権限を確認します。この「信用しない設計」が、結果として全体の安定性を生んでいます。自分のコードを書くときも、同じ姿勢が役立ちます。「この入力は正しいはず」ではなく「正しくないかもしれない」から始める。OSはその見本です。

📌 注目ポイント

この記事の核心を4点に絞ります。

第一に、プロセスは「調理中の料理」です。 レシピ(プログラム)とは別物であり、状態を持ち、資源を占有します。1コアで多数が動いて見えるのは、ごく短い間隔で切り替えているからです。

第二に、仮想メモリは「嘘の住所」を配る仕組みです。 だから互いを壊せません。物理メモリより広く見せることもできますが、実際に超えると極端に遅くなります。

第三に、「ファイルを開く」は番号札を受け取る行為です。 番号札(ファイルディスクリプタ)は有限で、0・1・2は最初から配られています。閉じ忘れは EMFILE になります。そして書き込みはすぐにはディスクに届きません。

第四に、権限は「できること」を3×3で分ける仕組みです。 読み・書き・実行 × 所有者・グループ・その他。root はすべてを無視できるので、最小権限の原則が重要になります。

💡 活用事例:コンテナは「新しい箱」ではなく、OSの機能の寄せ集め

ここまでの話が、現代の開発でどう使われているかを見ます。コンテナです。

「コンテナは仮想マシンより軽い」とよく言われます。しかしなぜ軽いのかを説明できる人は多くありません。答えは、コンテナが新しい機械を作っているのではなく、この記事で扱ったOSの機能を組み合わせて「別のマシンのように見せている」だけだからです。

具体的には2つの仕組みを使っています。第一に、名前空間(namespace)。これは検証②の仮想メモリと検証①のプロセスの考え方を応用したもので、プロセスに「見える範囲」を制限する仕組みです。ファイルシステム、プロセス一覧、ネットワーク、ホスト名など、それぞれについて「自分の世界」を持たせます。コンテナ内から他のコンテナが見えないのは、見えないようにOSが設定しているからです。

第二に、cgroup(コントロールグループ)。これは検証①の「資源の配分」の仕組みで、CPU時間・メモリ量・I/O量に上限を設定するものです。コンテナに「メモリ512MBまで」と決められるのは、この仕組みのおかげです。

仮想マシンとコンテナの構造を比較した図 仮想マシンはハードウェアを模擬してOSを丸ごと起動するため重く、コンテナはホストのカーネルを共有して見える範囲と資源の上限だけを分けるため軽いことを示す図。 仮想マシンは「OSを丸ごと増やす」、コンテナは「OSに区画を作る」 仮想マシン ── 重いが強い分離 ゲストOS ①(カーネル) 丸ごと起動するので重い ゲストOS ②(カーネル) 別のOSも動かせる(LinuxとWindowsなど) ハードウェアを模擬する層(ハイパーバイザ) ホストOS コンテナ ── 軽いがカーネルは共有 コンテナ A 見える範囲を区切る =名前空間 資源の上限も決める =cgroup コンテナ B 「別のマシンに 見える」だけ プロセスの一種なので 起動はプロセス並み カーネルは1つだけ(ホストのものを共有) だからLinuxカーネル用のコンテナはLinux上でしか動かない どちらも「隔離」だが、隔離する層が違う。コンテナは2階の機能を使い、仮想マシンは1階ごと作り直す
観点仮想マシンコンテナ
何を増やすかハードウェアを模擬してOSを丸ごと起動する既存のカーネルを共有し、見える範囲と上限だけを分ける
起動の重さOSの起動を含むため重いプロセスの起動とほぼ同じ軽さ
使うOSゲストOSを自由に選べる(Linux以外も可)ホストのカーネルに制約される
分離の強さ強い(別のカーネルなので境界が厚い)ホストのカーネルを共有するため、境界は仮想マシンほど厚くない
使っている仕組みハイパーバイザ(1階の模擬)名前空間とcgroup(2階の機能)

ここからジュニアエンジニアが持ち帰れる教訓は3つあります。第一に、「軽いから安全」ではありません。コンテナはホストのカーネルを共有するので、分離の境界は仮想マシンより薄い。だから信頼できないコードを動かす場面では、仮想マシンが選ばれます。第二に、コンテナのトラブルはOSのトラブルです。Killed はcgroupのメモリ上限、権限エラーは名前空間とユーザーの設定。第1回の「何階の話か」でいえば、コンテナは2階の設定です。第三に、OSの仕組みを知っていれば、コンテナは魔法ではなくなる。「なぜ軽いのか」「なぜ動かないのか」が、すべて資源と隔離の言葉で説明できます。

✅ 要点まとめ

読み終えたあなたが持ち帰るべきエッセンスを、6つに圧縮します。

  • OSの仕事は「限られた資源の配分」と「住人どうしの保護」の2つに還元できる。エラーコードはどちらが働いたかを教える
  • プログラムはレシピ、プロセスは調理中の料理。独立した記憶空間を持ち、状態とPIDを持つ
  • 1コアで多数のプロセスが動くのは、ごく短い間隔で切り替えているから。切り替えにはコストがある
  • 仮想メモリは「嘘の住所」を配る仕組み。だから互いを壊せない。物理量を超えると急激に遅くなる
  • 「ファイルを開く」は番号札(ファイルディスクリプタ)を受け取ること。0・1・2は最初から配られている。書き込みはすぐにはディスクに届かない
  • 権限は 読み・書き・実行 × 所有者・グループ・その他 の9ビット。rootは無視できるので最小権限が重要

🚀 取り込み方

「明日から使うには何をすればいいか」を、期間ごとに分けて示します。

今日(5分でできること)

自分のマシンで、OSが今どんなプロセスを動かしているかを見てください。数字を見るだけで、この記事の話が自分の環境の話になります

  • Linux / macOS: ps aux | head -20 で一覧、ps -o pid,ppid,stat,cmd で状態(STAT列)まで見る。free -h でメモリの残量
  • Windows(PowerShell): Get-Process | Sort-Object -Property WS -Descending | Select-Object -First 15 Name, Id, WS でメモリ消費の多い順に表示(WSはワーキングセット=実際に使っている物理メモリの目安です)

さらに、自分のプロセスが同時に開けるファイル数を確認してみてください。LinuxやmacOSなら ulimit -n です。「思ったより多い/少ない」のどちらでも、これが EMFILE の正体だと分かります。

今週(小さく試す)

担当コードから、次の3つを探してください。(1) ファイルや接続を開いて閉じ忘れていないか、(2) 書き込んだ直後に処理が落ちても消えてはいけないデータはどれか、(3) 相対パスでファイルを指定している箇所。見つけたら、すぐ直さずに「OSのどの資源に関係するか」を1行メモしてください。これだけで、次にエラーが出たときの切り分けが速くなります。

シェルのリダイレクトが「番号札の付け替え」であることを、次のコマンドで確認できます。標準出力と標準エラーを別々のファイルに分けてみてください。

# 標準出力は out.log へ、標準エラーは err.log へ
python -c "import sys; print('通常の出力'); print('エラー出力', file=sys.stderr)" > out.log 2> err.log

# それぞれの中身を確認
cat out.log
cat err.log

同じ1つのコマンドの出力が、番号札の向き先を変えるだけで別々のファイルに分かれました。これはOSの仕組みをそのまま使っているだけです。

今月(業務に組み込む)

チームに次の3点を提案できないか検討してください。(1) ファイルや接続は必ず閉じる(言語の構文で自動化できるならそれを使う)、(2) 消えては困る書き込みの後はディスクへ同期する、(3) コンテナのメモリ上限と実際の使用量を可視化するどれも「OSの資源を意識する」という1つの姿勢にまとまります。この姿勢は、障害対応で「メモリか、番号札か、権限か、パスか」を最初に切り分けられる人材につながります。

🔥 ハマりポイント

つまずきやすい5つの落とし穴を、「〜と思いがちだが、実は〜」の形で整理します。

その1:Killed はアプリのバグだと思いがちだが、実はOSが止めている

症状は、エラーログもスタックトレースもなくプロセスが消えること。原因は、メモリの使いすぎでOS(またはcgroup)が強制終了させたこと。対処法は、メモリ使用量の推移を記録することです。ログが出ないのは、止めたのがアプリではなくOSだからです。Linuxなら dmesg にOOM Killerの記録が残っていることがあります。

その2:書き込めばファイルに残ると思いがちだが、実はバッファに溜まっている

症状は、プロセスが落ちた直後のログが残っていないこと。原因は、OSが書き込みをメモリに溜めてからディスクへ書いていたこと。対処法は、重要な書き込みの後で明示的に同期することです。性能とのトレードオフなので、すべてではなく重要な箇所だけに適用してください。

その3:権限エラーはファイルの権限だけを見ればよいと思いがちだが、実は実行ユーザーも見る

症状は、権限を 777 にしたのにまだ EACCES になること。原因は、実行しているユーザーが想定と違うこと(コンテナ内のユーザーなど)。対処法は、「誰として実行しているか」を先に確認することです。権限を緩める前に、id コマンドでユーザーとグループを確認してください。

その4:プロセスを増やせば処理が速くなると思いがちだが、実は切り替えコストが増える

症状は、並列度を上げたのにスループットが伸びない、あるいは下がること。原因は、コンテキストスイッチのコストとキャッシュの汚れ。対処法は、並列度を1から順に上げて計測することです。第3回で見たとおり、キャッシュは有限なので、切り替えが多いほど効率が落ちます。

その5:仮想メモリがあるからメモリ不足にならないと思いがちだが、実は急激に遅くなる

症状は、メモリを使い込んだあとに処理が数十倍遅くなること。原因は、物理メモリを超えて退避と復元が繰り返されていること。対処法は、「動いているか」ではなく「どれくらいの速度で動いているか」を見ることです。仮想メモリは容量を保証しますが、速度は保証しません

🔄 比較:同じ「隔離」でも手段が違う

最後に、隔離と権限の手段を比較します。「どれが強いか」ではなく「何を守りたいか」で選ぶのがポイントです。

手段何を分けるか強み弱み・限界
プロセス実行の単位とメモリ空間OSの基本機能。軽くて確実ファイルシステムやネットワークは共有される
ユーザー権限操作できる範囲設定が単純。事故と横展開の両方を防ぐ設定ミスがそのまま穴になる(過剰な権限付与など)
コンテナ見える範囲と資源の上限軽い。配布と再現が容易カーネルを共有するため分離の境界は薄い
仮想マシンハードウェア全体(カーネルも別)分離が厚い。別OSも動かせる起動とリソースのコストが大きい

この表から持ち帰ってほしいのは、「軽さと分離の強さはトレードオフ」という1行です。そして、信頼できないものを動かすときは、より厚い境界を選ぶ。これはセキュリティの設計でも、テスト環境の設計でも同じ原則です。

📅 今後の展望

OSの役割は、これからどうなるのでしょうか。方向性は3つ考えられます。

第一に、OSの抽象はさらに厚くなるということです。コンテナ、サーバーレス、マネージドサービスと、下の階を見せない技術が増え続けています。しかし第1回で確認したとおり、抽象が厚くなるほど、漏れたときに降りる階数が増えますKilled の原因を調べるのに、アプリ・コンテナ・オーケストレータ・クラウドの4層をたどる必要がある。これは今後さらに増えるでしょう。

第二に、資源の配分が細かくなる方向です。cgroup は v2 で複数のコントローラを単一の階層に統合し、資源の配分を一元的に扱えるようになりました。CPUの割り当てを細かく指定する仕組みも整備されています。「誰にどれだけ配るか」というこの記事の中心テーマは、これからもっと精緻になります

第三に、分離の境界が再設計される方向です。コンテナの分離をより厚くする技術(軽量仮想マシンなど)が実用化されてきました。「軽さと分離の強さはトレードオフ」という前述の原則に対して、その中間を狙う動きです。

なお、この記事で扱った4つの仕組みは、Unixが1970年代に確立した設計を基本にしています。50年以上たっても構造が変わっていないのは、「資源の配分と保護」という問題設定そのものが変わっていないからです。この安定性が、OSの知識が長く効く理由です。第1回で「下の階ほど長寿」と書いたのは、まさにこのことです。

🗺️ 次回予告:なぜ「探し方」で所要時間が変わるのか

第4回では、OSが「配る」「守る」という2つの仕事をどう実現しているかを見ました。プロセス、仮想メモリ、ファイル、権限。ここまででコンピュータの下の階(1階と2階)の話が一通り終わります

第5回は、視点を上に戻してアルゴリズムとデータ構造を扱います。同じ100万件のデータを探すのに、なぜ数ミリ秒で終わる方法と数秒かかる方法があるのか。並べ替えの手間を先に払うと、なぜ後で得をするのか。そして、O(n)O(log n) という記法が何を約束していて、何を約束していないのか

第3回で扱ったメモリの階段と、第5回で扱う計算量は、性能を語るための両輪です。片方だけでは「なぜ遅いか」を説明しきれません。次回はそのもう一方の軸を手に入れる回になります。

まとめ

この記事を読んだあなたは、Killed を見たときに「アプリのバグだ」と即断しなくなります。まず「どの資源が足りなかったのか」を考え、EACCES なら誰として実行しているかを確認し、EMFILE なら開きっぱなしを探す。エラーコードが管理人の言葉として聞こえるようになります。

そして、open という1行が番号札の受け取りであること、fork が料理をもう1つ増やす行為であること、権限が9ビットの鍵束であることが、仕組みとして見えるようになります。OSは普段は姿を見せませんが、あなたの書いたコードのすべては、この管理人の上で動いています。その管理人が何を配り、何を守っているかを知っていることが、静かに壊れるソフトウェアと、そうでないソフトウェアの差になります。

参考文献

  1. Remzi H. Arpaci-Dusseau, Andrea C. Arpaci-Dusseau, “Operating Systems: Three Easy Pieces”(仮想化・並行性・永続性の三部作) — https://pages.cs.wisc.edu/~remzi/OSTEP/
  2. Abraham Silberschatz, Peter B. Galvin, Greg Gagne, “Operating System Concepts”(通称 Dinosaur Book) — https://www.os-book.com/
  3. The Open Group, “POSIX.1-2017 (IEEE Std 1003.1-2017)”(fork・open・read などの標準仕様) — https://pubs.opengroup.org/onlinepubs/9699919799/
  4. Linux man-pages project, “Linux Programmer’s Manual”(fork(2)execve(2)open(2)fsync(2)mmap(2) など) — https://www.kernel.org/doc/man-pages/
  5. Michael Kerrisk, “The Linux Programming Interface”, No Starch Press(Linux/UNIXシステムプログラミングの決定版) — https://man7.org/tlpi/
  6. Dennis M. Ritchie, “The Evolution of the Unix Time-sharing System”, 1979/1984(プロセスとファイルという設計思想の原典) — https://www.bell-labs.com/usr/dmr/www/hist.html
  7. Gustavo Duarte, “Anatomy of a Program in Memory”, 2009(プロセスの仮想メモリ配置を図解した解説) — https://manybutfinite.com/post/anatomy-of-a-program-in-memory/
  8. Linux Kernel Documentation, “Control Group v2” — https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html
  9. Linux man-pages project, “namespaces(7)”(名前空間の一覧と概要) — https://man7.org/linux/man-pages/man7/namespaces.7.html
  10. Docker Inc., “What is a Container?”(公式ドキュメント) — https://www.docker.com/resources/what-container/
  11. Open Container Initiative, “OCI Runtime Specification” — https://github.com/opencontainers/runtime-spec
  12. Google, “Borg, Omega, and Kubernetes”, ACM Queue, 2016(大規模クラスタ管理の設計思想) — https://queue.acm.org/detail.cfm?id=2898444
  13. Jerome H. Saltzer, Michael D. Schroeder, “The Protection of Information in Computer Systems”, 1975(最小権限の原則の原典) — https://web.mit.edu/Saltzer/www/publications/protection/
  14. 独立行政法人情報処理推進機構(IPA), 「基本情報技術者試験 シラバス」(OSの機能・プロセス管理・メモリ管理・ファイル管理) — https://www.ipa.go.jp/
  15. man7.org, “Linux System Calls”(システムコール一覧) — https://man7.org/linux/man-pages/dir_section_2.html
  16. ACM/IEEE-CS Joint Task Force, “Computer Science Curricula 2023 (CS2023)”(Operating Systems 領域) — https://csed.acm.org/
ジュニアエンジニア向け コンピュータサイエンス入門 ── 全8回の一覧
いま読んでいるのは 第4回 です。読みたい回から始めても構いません。
  1. 第1回:全体地図:その1行は「5階建てのビル」で動いている
  2. 第2回:データの正体:0.1+0.2はなぜ0.3にならないのか
  3. 第3回:CPUとメモリ:1回のメモリアクセスは100回の計算より高くつく
  4. ▶ 第4回:OSの仕事:「ファイルを開く」は何をしているのか(この記事)
  5. 第5回:アルゴリズムとデータ構造:100万件から1人を探すのに20回で足りる
  6. 第6回:ネットワーク:クリックした荷物はどうやって海を渡るのか
  7. 第7回:データベース:100人が同時に書き換えても壊れないのはなぜか
  8. 第8回:並行と並列:awaitを1つ忘れただけで、なぜ本番だけ壊れるのか

© Copyright 2005-2026| Rui Software | All Rights Reserved