Ruleru
LLM を聞いて学ぶ

10/27(火) · 12:08

第3章 前半: OSの保護とプロセス、スレッドと共有

『日本語技術教科書 Vol.0』の第3章 前半。OSの保護とプロセス、スレッドと共有を、2 人の掛け合いで解説する。

この回を聴く

AIこの記事は AI の Claude が書きました。

  • 保護は、権限の区別、システムコールという窓口、例外で成り立ち、権限の切り替えと走る主体の入れ替えは別の出来事である。
  • プロセスは番地の体系を分けて隔離し、スレッドは同じ番地の体系を共有するため、同期の責任が生まれる。
  • 競合は 1 つの更新が複数の段階に分かれることから起き、ロックは参加者全員の申し合わせで、取る順の乱れはデッドロックを招く。

教材の 3.1 節から 3.3 節まで。

3.1.1〜3.1.2 OS が資源を守る仕組み

『日本語技術教科書 ボリュームゼロ』の第3章前半は、守る側の話から始まる。アプリが自分の対応表を自由に書き換えられたら、A の番地を B のページへ向けられてしまい、隔離が壊れる。そこで表を変えてよい主体を絞る。その中心がカーネルである。CPU には権限の違う 2 つの動き方があり、通常のアプリはユーザーモードで表や装置を直接いじれず、扱えるのはカーネルモードの間だけになる。ユーザー空間とカーネル空間は置き場所の区分、モードは走っている命令の権限で、別の区別だ。

書き込みが許されないページへ書くと処理が止まり、OS が呼ばれる。まだ用意されていないだけか、本当に違反かを OS が見極める。型の食い違いを咎める JavaScript、例外を投げる Python、不正アクセスを見つける OS は、それぞれ別の層の仕事である。守りには手間も伴い、小さな要求を大量に出すと窓口を通る費用が目立つ。かといってカーネルに何でも置けば、誤りの影響が広がる。

アプリが OS に頼む窓口がシステムコールで、関数呼び出しのすべてが通るわけではない。読み出しの例では、ファイルを指す番号 3、置き場の番地、長さ 4 バイトを渡し、カーネルが指す先、許可、置き場に書けるかを調べる。使えるデータが 3 バイトなら戻り値は 3 で、頼んだ 4 は返る量の約束ではない。データが未到着なら頼んだ側は待機に入り、完了後は走れる状態に戻るが、実際に走るのは順番が来てからである。窓口を通ることと別のプロセスへ入れ替わることは別で、1 回通るたびに入れ替わるという理解は誤りだ。

3.2.1〜3.3.1 プロセスとスレッド

保存されているのは設計図にあたる実行ファイルで、動き出すと、OS が住まいと状態をプロセスとして用意する。同じ Python を 2 回起動しても住まいは別で、一方の辞書が他方に現れることはない。教材の数字では、A も B も 16 進で 4010 番地を使うが、A は 9010、B は C010 へ結ばれる。共有したいなら、同じ物理ページへ結ぶ必要がある。

プロセスを増やすのが fork で、呼んだ側を基に子を作る。exec は子の中身を別のプログラムに入れ替え、成功すると元の次の行へは戻らない。大きなプロセスの全複製を避けるため、最初は同じページを共有し、書く瞬間にだけ分けるコピーオンライトを使う。子が書くと、親には 7、子には 8 が見える。1 バイトの更新でも例では 4 キビバイトを複写するので、fork がいつでも無料とは言えない。終了後は、親が結果を受け取るまで記録が残り、これをゾンビと呼ぶが、計算もメモリの占有もしていない。

CPU 時間の配分では、続きの位置やレジスタを保存して、止めた流れを後で戻す。状態は、走っている、走れる、待っているの 3 つである。教材の時間割では、A が 2 ミリ秒で I/O 待ちになり、B が 2 から 5 ミリ秒を使い、A の待ちは 4 ミリ秒で終わるが、走れるのは 5 ミリ秒からになる。A が使った CPU 時間は 3 ミリ秒、経過は 6 ミリ秒である。切り替えを細かくすると待たせにくくなるが、仮定の 5 マイクロ秒の費用が 1 ミリ秒ごとなら約 0.5 パーセント、100 マイクロ秒ごとなら約 4.76 パーセントになる。

スレッドは同じプロセスの中で独立した実行の流れを持ち、番地の体系と資源を共有する。ルームシェアのように、台所は共用で、各自の部屋であるスタックと実行状態は別々だ。ただし部屋が別でも鍵は閉まっておらず、参照を渡せば触れる。並行は仕事が入り交じって進むこと、並列は実際に同時に動くことで、待つ仕事を増やしても同時計算の量は増えない。

3.3.2〜3.3.3 競合とロック

共有カウンタが 0 で、2 つのスレッドが 1 ずつ足すとする。両方が 0 を読んでから書くと、結果は 1 にしかならない。1 を足す操作が、読む、計算する、書くに分かれ、その隙間に割り込まれたからだ。割り込まれない一まとまりの性質が原子性である。2 つの口座を別々に守れても、合計 100 という関係は途中で崩れ、10 を引いて足す前の瞬間には 90 に見える。

相互排他ロック(mutex)を使うと、ある区間に同時に入れる流れは 1 つだけになる。読むところから書くところまでをまとめて守らなければ、古い 0 から 1 を作る問題は残る。ロックは変数に貼られた札ではなく、参加者全員の申し合わせである。取れないときの待ち方には、確認を繰り返すスピンと、OS に眠らせてもらう方式がある。条件変数を使うときは、起きたあとに条件を確かめ直す。

デッドロックは、互いに相手の持つものを待って進めなくなる状態だ。たとえば、2 つのロックを逆の順に取ろうとして、どちらも手放す所まで進めなくなる。起きるための必要条件は、独占、持ったまま追加を待つ、奪わない、循環して待つの 4 つで、輪があれば必ず起きるとは一般化されていない。取る順を L1 から L2 へそろえれば、この例の循環は防げる。

次は第3章の後半で、ファイル記述子とパイプ、環境、パス、コンテナ、Python の GIL と複数のプロセスを扱う。

出典

  • オーナー(AI で作った教材、Web には公開していない) 日本語技術教科書 Vol.0 コンピュータからPython・機械学習・LLMへ 第3章 2026-10-18