Sionの技術ブログ

SREとして日々の学習を書いて行きます。twitterは@sion_cojp

プロセスって何?





はじめに




そもそもOSは

f:id:sion_cojp:20170223172326p:plain:w500

  • OSではマルチタスク機能によって、細かくプロセスを切り替えることで"同時に動かしてる様に見せる"
  • 並列ではなく並行。
  • Webブラウザやメーラーなど複数アプリを実行してるときマルチタスク機能を使っている。




プロセスって何?

  • 実行状態にあるプログラム
  • アプリは(複数の)プロセスから作られてる
  • CPU(プロセッサ)は1度に1つのプロセスしか処理できない




プロセスの動き

f:id:sion_cojp:20170228164344p:plain:w500

  • プロセスは実行準備、実行、待ちの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って何?

f:id:sion_cojp:20170223172335p:plain:w500

  • プロセス毎に物理メモリ領域を確保->その領域にアクセスするための仮想アドレスを用意する
    • 同一OS内のすべてのプロセスは同一サイズの仮想アドレスを持つ
    • CPUの中にMMUと仮想アドレスがある。メモリ上にはない
    • プロセスが「メモリを使用する」宣言すると確保される
  • プロセス->仮想アドレス->物理アドレス




仮想アドレスのメリットは?

f:id:sion_cojp:20170327020132p:plain:w500

  • 他のプロセスからアクセスできないように制御されてるので、セキュア
  • 物理メモリはバラバラでも、仮想アドレスは連続した領域のように扱えるため、メモリの隙間も有効に使える
  • 図のように、同じ物理メモリをマッピングすることで、複数のプロセスでメモリを共有できる
    • 共有ライブラリのロードとか




複数のプロセスが共有してるデータにアクセスすると競合(レースコンディション)しない?

  • もし、あるプロセスが共有メモリを読んでいるなら、 他のプロセスは「書きこみ終了するのを待つ 機構」を作る必要があるよね
  • UNIXでは、そのようなプロセス間通信(IPC)に下記手法があるよ
  • 例えばセマフォ排他制御
    • 0,1の値を保持しておき、0の場合はアクセスNG、1の場合はアクセスOKなど。
    • 解放出来なかった(この場合だとずっと1にならない)場合、デッドロックって言うよ
  • ちなみにGolangでは「メモリ共有より、メッセージ共有」を推奨している




プロセスがメモリに保持するデータってどんなの?

f:id:sion_cojp:20170327020230p:plain:w500

  • 上位アドレスはCPUのビット数と、メモリの数によって変わる
    • 32bit OSだったら、232 = 0xFFFFFFFF = 4GB(先ほども出ましたね)
  • 大きく3つに分かれる
  • ①テキストセグメント
    • 実行されるプログラム。
  • ②データセグメント
    • 静的領域:定数、グローバル変数など。データ(初期化ありとBSS(初期化なし))がある
    • ヒープ:malloc()などで動的に確保。GCでクリア。
  • ③スタックセグメント
    • スタック:CPUのレジスタを一時的に退避させたりする。ローカル変数を確保。上位アドレス側には引数と環境変数が置かれる。




スタックって何?

  • 処理中のデータを一時的に退避(入れたり出したり)するデータ構造
  • 最後に入った要素が最初に取り出されるので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
// ......