Linuxシステムプログラミング8章
Linuxシステムプログラミングの8章メモリ管理の雑なメモ。 下に行くほど雑になってそう。この本。

- 作者: Robert Love,ロバートラブ,千住治郎
- 出版社/メーカー: オライリージャパン
- 発売日: 2008/04/16
- メディア: 大型本
- 購入: 5人 クリック: 181回
- この商品を含むブログ (31件) を見る
プロセスアドレス空間
ページとページイン/アウト
- 仮想アドレス空間はページで構成される
- サイズはマシンによる 32だと4KB、64ビットマシンだと8KB
- ページには有効と無効という概念がある
- 有効なページは実際に対応する物理メモリ、またはディスク上のファイルがが存在する
- 無向なページはアクセスするとセグフォになる
- プログラムが物理メモリに存在せず二次記憶装置に存在するページにアクセスしようとすると、ページフォルトをMMUが発生させる
ページ共有とcopy-on-write
- (異なるプロセスの)異なる仮想アドレスから物理メモリ内のデータを共有できる
- 読み書き専用、読み取り専用どちらでも良い
- 書き込む処理は二種類ある
メモリ領域
動的メモリ割り当て
- 動的メモリは、コンパイル時ではなく実行時に割り当てるもの
- 実行時になるまで、実際のサイズなどはわからない
- cだと
malloc
で動的メモリ割り当て出来る- 成功すると、割り当てたメモリ領域の先頭アドレスを返す
配列割当
- 配列の割当には
calloc
をつかうmalloc
と違って0で初期化する
メモリ領域のサイズ変更
realloc
でメモリ領域のサイズを変更できる
動的メモリの解放
- 動的に割り当てたメモリは明示的に解放するまでプロセスアドレスの一部として存在し続ける
free
を使う- 開放しないとメモリリークする
- 解放後のメモリにアクセスしてはいけない(ダングリングポインタ)
アラインメント
- ハードウェアからみたメモリ領域とアドレスの関係
- データサイズの定数倍のメモリアドレスへ配置された変数を自然なアライメントという
- 32ビットの変数がメモリ上で4の倍数のアドレスに配置されているとき
アラインメントされたメモリの割当
- コンパイラとCライブラリがよしなに解決してくれる
- 32ビットシステムでは8バイト,64ビットだと16バイト境界にアライメントされる
posix_memalign
を使用すると、任意の倍数でアライメントされたsizeバイトの動的メモリを割り当てられる
他のアライメント問題
- 非標準の型
- 構造体のアライメントはメンバの最大サイズに合わせる
- 最大サイズを持つメンバが4バイト境界でアライメントされる32ビット整数なら、その構造体は4バイト以上のアライメント
- パディング(padding)が必要。
- char(1バイト)のあとにintのメンバ(4バイトでアライメントされる)がある場合、charの後ろに3倍とのパディングが必要
- 共用体のアライメントはメンバの最大サイズに合わす
- 配列アラインメントは要素に合わせる
- 構造体のアライメントはメンバの最大サイズに合わせる
- ポインタ
- 以下の例では、unsigned longは4 or 8バイト境界に割り当てられるのに、charはほぼ確実に1バイト
- そこで c をロードするとアラインメント違反
- 違反の結果はアーキテクチャ依存でクラッシュすることもある
char greeting[] = "Ahoy Matey"; char *c = greeting[1]; unsigned long badnews = *(unsigned long *) c;
データセグメントの管理(ヒープ領域)
- でたーセグメントを直接操作する関数はあるが、
malloc
などが使いやすく機能も豊富であるためほぼ使われていない brk
はデータセグメントの末尾(break point)を渡された場所を変更する
無名メモリマッピング
- 典型的な
malloc
はデータセグメントを2の累乗のサイズの領域が並んだ状態に分割し,要求されたサイズに最も近い領域を貸す- 直前の領域がフリーの場合はつなげて,一つの大きなフリー領域にする
- 戦闘だった場合は領域を縮めてカーネルに返す
- バディメモリ割り当てシステムという
- 内部フラグメンテーション - 要求されたメモリより大きなメモリ領域を返す問題
- 外部フラグメンテーション - サイズ分のメモリ領域があるにも関わらず,複数のメモリ領域に分断されている
領域Bと領域Aが隣接しており,領域Aがbreakpointに隣接している場合,Bを開放してもAが終わらない限り開放されない
巨大なメモリ割り当てをするとglibcはヒープを使用せす無名メモリマッピングをする
- ファイルを使わない
- 巨大で,0に初期化された領域を使用できる
- 本来のヒープの外にあるのでフラグメンテーションが発生しない
- メモリ領域はサイズ,アクセスパー密書の変更が可能
- 短所
- メモリのサイズがページングサイズの整数倍になる,要求サイズがページサイズより小さいと無駄な領域が発生する
- カーネルからのヒープ割当に比べてオーバヘッドが大きい
無名メモリマッピングの作成
munmap
システムコール(mmpa
に MAP_ANONYMOUSを指定しても行ける)
/dev/zero のマッピング
高度なメモリ割り当て
mallopt
を使用してカーネルパラメータを変更できる。
メモリ割り当てのデバッグ
メモリ割り当て統計情報
mallinfo
でメモリ割り当ての統計情報を得る
スタック上のメモリ割り当て
- スタックはプログラムのオート変数(automatic variable)を格納する場所
alloca
を使ってスタック上で動的にメモリ割り当てをする- メモリ領域はスタック上に取られる
- つまり、
alloca
を実行した関数がリターンしたら解放/破棄される -> freeしなくていいのでコードが簡潔に alloca
を関数コールのパラメータ内で使用すると、関数パラメータの用のスタック領域にメモリを割り当ててしまうため使用を避ける
スタック上へ文字列をコピー
- 文字列の一時的なコピー等に使える
- そのためのインターフェイスとして、
strdupa strndupa
がある
- そのためのインターフェイスとして、
メモリ割り当て方法の選択
- malloc
- 単純だが割り当てられたメモリがゼロクリアされていない
- calloc
- 配列の割当が容易でゼロクリアされてる、配列以外には使いづらい
- realloc
- メモリサイズを変更できる、ソレ以外使えない
- brk and sbrk
- ヒープを完全に制御できる、低位すぎるインターフェイス
- 無名メモリマッピング
- 共有可能、アクセスパーミッションもできる。サイズが大きいときにも使える。メモリが小さいと効率が落ちる
- posix_memalign
- 任意サイズの境界に従うメモリを割り当てる。移植性に難がある
- memalign and valloc
- alloca
- 高速なメモリ割り当て、解放不要、サイズの大きな割当には不向き。
- 可変サイズ配列
- allocaとほぼ同じ、スコープを抜けたときに解放される。配列に限り有効
メモリ操作
バイト設定
- memset(void *s,int c,size_t n) sのメモリ領域へcをnバイト分設定
バイト比較
- memcmp(s1,s2,n) s1の先頭からnバイトs2と比較
- 構造体は持つ内部にパディングを含むため正しく比較できない(パディングは不定値だから)
バイト移動
- memmove(dst,src,n) srcの先頭nバイトをdstへこぴー
- メモリ領域が重なっていない場合も正しく動く
- もともと重ならないとわかっている場合にはmemcpyを使用する
バイト検索
- memchrはメモリ領域内の指定されたバイトを検索する
メモリのロック
- linuxのカーネルは不要なページはスワップアウトして、必要に応じてスワップインするデマンドページングを使用している
- この操作は透過的に行われるのでアプリケーションは気にしなくて良い
- しかし意識したい場合もある
- 決定論
- 時間的な制約を持つアプリケーションではページフォルトしてディスクIOが発生して時間的制約を超過してしまう
- セキュリティ
- 機密情報がページアウトされてしまうと、暗号化されずディスクへ保存されてしまう
- 決定論
- 当然全体のパフォーマンスが落ちる可能性があるので気をつける
アドレス空間の一部をロック
- mlock(addr, len)でアドレスの範囲を指定して物理メモリ上にロックする
アドレス空間全体をロック
- mlockallで(カレントプロセスの)メモリ全体を物理メモリ上にロックする
メモリのアンロック
- munlock, munlockall
メモリの遅延割当
- カーネルへメモリの追加を要求すると,カーネルは実際には物理メモリを割り当てずに成功を返す(commit)
- 書き込んだ時点で初めて割り当てる(CoW)
- 遅延できる
- 実際に使用する分(割当要求分ではない)のみ物理メモリを使用する(ページ単位にする)
- コミットしたメモリサイズをシステムの持っているメモリよりも大きくすることが出来る。(overcommit)