気になったことのあれこれ。この広告は、90日以上更新していないブログに表示しています。2.6.11のコードリーディングをしていく。CPUのアーキテクチャは書籍に沿ってIntelのx86とする。今回はプロセスのアドレス空間について見ていく。メモリ割り当ての指針カーネルからメモリ要求は最優先とし即座にメモリ割り当てを行い、且つ当該処理にバグが含まれないことを前提にエラーへの対策は必要以上には行わない。一方でユーザプロセスからのメモリ要求は緊急でないとし、リニアアドレスに対するアクセス権のみを付与し割り当ては遅延させる。実際にアクセスがあるまでページフレームの割り当てを行わず、アドレッシングエラーなども必ず補足する。プロセスのアドレス空間プロセスのアドレス空間はリニアアドレスを用いたアドレス空間として独立しており、カーネルはそのアドレス空間に対してリージョンの追加・削除を動的に行う。メモリリージョンは先頭リニアアドレス、サイズ、アクセスを保持する。サイズは4KBである。以下のような場合にプロセスは新たなにメモリリージョン(アドレス空間)を得る。コンソールにコマンドを入力するとシェルがプロセスを生成する過程で実行のためのアドレス空間を割り当てる実行中のプロセスが新たなプログラムを実行する際に、既存のメモリリージョンを開放し、新たなメモリリージョンがプロセスに割り当てられる。プロセスのユーザモードスタックにデータが増え続け、拡張が必要になった際に新たなメモリリージョンを割り当てる。他のプロセスとデータを共有するためにIPC共有メモリリージョンを作成することがある。プロセスがmalloc()を使用して動的メモリ領域(ヒープ)を拡張する。共有メモリリージョンを取り外すカーネルはプロセスのアドレス空間を常に認識する必要があり、以下の無効なリニアアドレスへのアクセスは例外ハンドラによって補足する必要がある。プログラミングエラーにより発生する場合リニアドレスはアドレス空間内のものだがページフレームが割り当てられていない場合ページフレームが割り当てられていない場合は異常ではなく、ページフォルトの例外によって実際にページフレームが割り当てられた後処理を再開する。メモリディスクリプタアドレス空間の情報はメモリディスクリプタ(mm_struct)により管理され、プロセスディスクリプタ(task_struct)のメンバであるmmから参照される。*/};メモリディスクリプタは双方向リストになっておりmmlistメンバで相互に接続されている。リストの先頭要素はinit_mm変数のmmlistメンバでシステムの起動時にプロセス0のメモリディスクリプタとして利用する。新たなメモリディスクリプタの獲得にはmm_alloc()関数を使用する。SLAB_KERNEL))上記を見ると内部でkmem_cache_alloc()を使用しており、スラブアロケータからメモリディスクリプタ用のオブジェクトを取得し、mmset()関数でゼロクリアしている。メモリディスクリプタの参照の解放mmput()関数は対象のメモリディスクリプタの参照カウンタ(mm_users)をデクリメントすし、結果が0になる場合にはメモリディスクリプタのリスト及び当該メモリディスクリプタを破棄する。}}カーネルスレッドのメモリディスクリプタカーネルスレッドは通常のプロセスと異なりTASK_SIZEよりも下位のリニアアドレスにアクセスすることはなく、メモリリージョンも利用しない。(0xC0000000)TASK_SIZEよりも上位のアドレスは(全てのプロセスで)カーネル空間となっているため、当該空間を管理するページテーブルはどのプロセスのモノでも関係がない。不必要なTLBやキャッシュのフラッシュを回避するため、カーネルスレッドは最後に実行した通常プロセスのページテーブルを利用する。これを達成するためにプロセスディスクリプタではmmとactive_mmの2つのメモリディスクリプタポインタを用意している。*active_mm;:active_mmは現在使用しているメモリディスクリプタを、mmはプロセスが保持してるメモリディスクリプタを指すポインタで、通常のプロセスであればmm及びactive_mmは同一のメモリディスクリプタを指している。カーネルスレッドはメモリディスクリプタを持たないためmmにはNULLが設定され、active_mmには直前に実行していたプロセスのメモリディスクリプタが設定される。カーネルプロセスがTASK_SIZEよりも高位なアドレスのページテーブルを更新する際には、全プロセスのページテーブルのエントリも更新する必要があるが、一度に全てを書き換えるは現実的ではない・そのため書き換えの際にswapper_pg_dirが保持している基準ページテーブルを更新し、実際にアクセスする際にページフォルトを発生させることで遅延処理として基準ページテーブルの情報を反映する。メモリリージョンメモリリージョンはvm_area_struct構造体で実装されている。*/#endif};メモリリージョンディスクリプタはリニアアドレスの区画を表している。vm_startでこのメモリリージョンのサイズが算出できる。カーネルは新たなメモリリージョンを割り当てる際に、隣のメモリリージョンを結合しようとする(アクセス権が異なる場合は結合できない)詳解Linuxカーネルvm_opsはvm_operations_struct構造体のポインタとして定義されメモリリージョンの操作を保持する。当該構造体は以下のように定義されている。addr);#endif};プロセスが保持するメモリリージョンはリストで管理されており、アドレスの昇順でソートされている。メモリディスクリプタ(mm_struct)のmmapメンバがリストの先頭を保持していて、メモリリージョンディスクリプタ(vm_are_struct)のメンバであるvm_nextがリスト内の次のメモリリージョンを指すことでリストを形成する。詳解Linuxカーネルメモリディスクリプタのmap_countメンバは保持ているメモリリージョンの総数を保持しており、当該値は/proc/sys/vm/max_map_countで変更することも可能。/proc/sys/vm/max_map_count65530特定のリニアアドレスを含むメモリリージョンの探索は簡単で、リストはアドレスの昇順に並んでいるため指定したアドレスよりも大きな終端アドレスを持つリージョンを見つけるだけとなる。単方向リストであるため検索/挿入/削除の処理コストはリストのサイズに比例して大きくなってしまい、データベースやデバッガのような数百~数千のメモリリージョンを用いるようなアプリケーションでは非常に効率が悪い。2.6では赤黒木のデータ構造を利用しメモリリージョンを管理する。参照や更新などの際は赤黒木からメモリリージョンの位置を特定する。メモリディスクリプタ(mm_struct)のmm_rbが赤黒木のルートノードを保持しており、各メモリリージョンはvm_rbでノードの色や親、個へのポインタなどを管理する。アクセス権メモリリージョンのアクセス権は以下のように定義されている。非線形マッピングメモリリージョンに対応するページテーブルエントリに同じ値(読み込み可能/書き込み可能/実行可能)を設定することでページング回路が直接アクセス権を確認することが可能となる。ページを追加する際にはメモリリージョンディスクリプタ(vm_area_struct)のvm_page_protメンバの値をページテーブルエントリのフラグに設定する。メモリリージョンに対する処理リニアアドレス空間の割り当て当該処理にはdo_mmap()関数を使用する。以下のように定義されている。ret;}file及びoffsetはファイルマッピング時に使用される、ここでは両者ともNULLであることを想定する。内部で呼び出されているdo_mmap_pgoff()関数は以下のように定義されている。error;}上記では大まかに以下の処理を行う新たな新たなリージョンのためのリニアアドレス空間を確保し、もし既存リージョンと連結可能であれば連結を行う(3へ)。連結に失敗した場合には新たにメモリリージョンとしてディスクリプタを作成しリスト及び赤黒木に繋ぐ。プロセスのアドレス空間サイズを更新し、確保したメモリリージョンのリニアアドレスを返す。リニアアドレス空間の解放リニアアドレス空間の解放にはdo_munmap()関数を使用する。削除対象の区間は単一のメモリリージョンとついになっている保証はなく、メモリリージョンの一部や複数のメモリリージョンにまたがっている場合がある。0;}上記ではバリデーションとアラインメントを行ってからメモリリージョンを適切に分割し指定の区間を開放している。»Profile低レイヤ好きのソフトウェアエンジニア。(1)はてなブログをはじめよう!k--onishiさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。