プロセスって何?
- はじめに
- そもそもOSは
- プロセスって何?
- プロセスの動き
- プロセスの切り替えってどうやってるの?
- プリエンプティブ方式と、ノンプリエンプティブ方式って何?
- プロセスの実行スケジュール管理はどうやってるの?
- プロセスとメモリの関係
- 演算って何?
- MMUって何?
- 仮想アドレスのメリットは?
- 複数のプロセスが共有してるデータにアクセスすると競合(レースコンディション)しない?
- プロセスがメモリに保持するデータってどんなの?
- スタックって何?
- malloc()って何?
- スタック領域とヒープ領域
- GCって何?
- スタックメモリって何?
- Golangでスタックオーバーフローを起こすには?
はじめに
- オペレーティングシステムの仕組みを読んで色々学んだことをメモ
- ときたまGolangの話題が出るかもしれません
そもそもOSは
- OSではマルチタスク機能によって、細かくプロセスを切り替えることで"同時に動かしてる様に見せる"
- 並列ではなく並行。
- Webブラウザやメーラーなど複数アプリを実行してるときマルチタスク機能を使っている。
プロセスって何?
- 実行状態にあるプログラム
- アプリは(複数の)プロセスから作られてる
- CPU(プロセッサ)は1度に1つのプロセスしか処理できない
プロセスの動き
- プロセスは実行準備、実行、待ちの3種類状態がある
- プロセスは常に1つのプログラムしか実行出来ないので、うまく切り替えてる。
プロセスの切り替えってどうやってるの?
- 切り替える手法は、プリエンプティブ方式と、ノンプリエンプティブ方式がある
プリエンプティブ方式と、ノンプリエンプティブ方式って何?
- プリエンプティブ:OSがプロセッサの実行権限を管理して、切り替える。現在のOSはこれ。
- ノンプリエンプティブ:プログラム自身に任せる。昔のOS。
- ちなみにgoroutineはノンプリエンプティブ方式を使って切り替えてる
プロセスの実行スケジュール管理はどうやってるの?
- OSによって色々ある
- 例えば、ラウンドロビン方式:実行可能状態にあるプロセスを順番に実行す
プロセスとメモリの関係
- CPUは、指定したプログラムコードがある場所(アドレス)にジャンプする命令を持っている
- アドレスはメモリ 1byte
- CPUのビット数でアドレスの数が決まる
- 32bit 232 = 0xFFFFFFFF ≒ 4GByte
- 64bit 264 = 0xFFFFFFFFFFFFFFFF = 16EByte ≒ 172億GByte
- までのアドレスを認識出来る(0xは16進数って意味)
- 誰も使っていないアドレスを探して利用する必要があるので、MMU(memory managemanet unit)がアドレス管理している
- CPUがプロセスを実行する際、そのプロセスが持っているアドレスに対して演算を行う
演算って何?
- 四則演算(足す、引く、かける、割る)
- 論理演算(AND・OR・NOT)
- など
MMUって何?
- プロセス毎に物理メモリ領域を確保->その領域にアクセスするための仮想アドレスを用意する
- 同一OS内のすべてのプロセスは同一サイズの仮想アドレスを持つ
- CPUの中にMMUと仮想アドレスがある。メモリ上にはない
- プロセスが「メモリを使用する」宣言すると確保される
- プロセス->仮想アドレス->物理アドレス
仮想アドレスのメリットは?
- 他のプロセスからアクセスできないように制御されてるので、セキュア
- 物理メモリはバラバラでも、仮想アドレスは連続した領域のように扱えるため、メモリの隙間も有効に使える
- 図のように、同じ物理メモリをマッピングすることで、複数のプロセスでメモリを共有できる
- 共有ライブラリのロードとか
複数のプロセスが共有してるデータにアクセスすると競合(レースコンディション)しない?
- もし、あるプロセスが共有メモリを読んでいるなら、 他のプロセスは「書きこみ終了するのを待つ 機構」を作る必要があるよね
- UNIXでは、そのようなプロセス間通信(IPC)に下記手法があるよ
- 例えばセマフォ(排他制御)
- 0,1の値を保持しておき、0の場合はアクセスNG、1の場合はアクセスOKなど。
- 解放出来なかった(この場合だとずっと1にならない)場合、デッドロックって言うよ
- ちなみにGolangでは「メモリ共有より、メッセージ共有」を推奨している
プロセスがメモリに保持するデータってどんなの?
- 上位アドレスはCPUのビット数と、メモリの数によって変わる
- 32bit OSだったら、232 = 0xFFFFFFFF = 4GB(先ほども出ましたね)
- 大きく3つに分かれる
- ①テキストセグメント
- 実行されるプログラム。
- ②データセグメント
- ③スタックセグメント
スタックって何?
- 処理中のデータを一時的に退避(入れたり出したり)するデータ構造
- 最後に入った要素が最初に取り出されるのでLIFOともいう(Last-In, First-Out)
- 同じデータ構造のキューは最初に入った要素が最初に取り出されるのでFIFO(First-In, First-Out)という
空のスタック [ Aをスタックに入れる [A Bをスタックに入れる [A B Bをスタックから取り出す [A ]
malloc()って何?
- memoryをallocate(確保)するための関数
- 必要な量を必要なときに動的にヒープ領域に確保できるのがメリット
- 通信プログラムだと、クライアントが接続してきたときに、メッセージ交換に使うメッセージ用のバッファを動的に確保したり。
- クライアントが切断したら、free()で解放できるので、柔軟にメモリが使える。
スタック領域とヒープ領域
- どちらも実行時にメモリを確保する領域
ヒープ | スタック | |
---|---|---|
領域確保 | 動的 実行時確保 |
静的 コンパイル時確保 |
解放 | 手動 | 自動 |
容量 | 大 | 小 |
速度 | 遅 | 速 |
GCって何?
- ヒープ領域がいっぱいになると新しいオブジェクトがロード出来なくてプログラムが実行できない
- GCは、ヒープ領域に空きがなくなると、使用されてないオブジェクトを判別してメモリ上から消去する
スタックメモリって何?
- タスクや、関数内だけで使われる変数やアドレス情報を保持するメモリ領域
- タスクを終了する or 関数から抜けると、スタックの内容も破棄される
この中にスタックを構築し、下記を保持していく。
- スタックポインタ (SP): スタック領域のどこを見ているかを指す値
- プログラムカウンタ (PC): プログラムのどこを実行しているかを指す値
ちなみにGolangでは、スタックメモリが不足すると、新しいチャンクが確保されて追加されるので、スタックオーバーフロー(スタックにメモリを積み過ぎて死ぬ)は発生しにくい
Golangでスタックオーバーフローを起こすには?
package main import "fmt" func main() { var a []interface{} a = make([]interface{}, 0, 10) a = append(a, append(a, 1)) fmt.Println(a) } // runtime: goroutine stack exceeds 250000000-byte limit // fatal error: stack overflow // ......