【詳解Linux Kernel】2章「メモリアドレッシング」読書メモ

詳解Linuxカーネルを読んでいたら2章にして意味不明過ぎて超次元サッカー状態に陥ったので、素読みで理解するのは無理と判断して読書メモをしたためることにした。
もやっとする所は太字にして後から調べる。

80x86プロセッサの仕組み

2.1メモリアドレス
メモリアドレスには3種類ある

論理アドレス→(セグメンテーション回路)→リニアアドレス→(ページング回路)→物理アドレス

論理アドレス機械語オペランドや命令のアドレスを指定するときに使う。セグメントとオフセットから成り、オフセットはセグメントの先頭から実アドレスまでの差を表す。ここでいうオペランドや命令ってなんだ???
リニアアドレス:0x00000000-0xffffffffの値をとる。世間でいう所の仮想アドレス。
物理アドレス:メモリチップ内のメモリセルの指定に使う。メモリセルの指定=プロセッサのアドレスピンを通じて電気信号をメモリバスへ送る

マルチプロセッサシステムでは複数のCPUが同じメモリにアクセスするのでメモリ調停回路君が良しなにロックを調整してくれる。
仮に単一プロセッサでもDMAコントローラが存在するからメモリ調停回路は必要になる。DMAコントローラって何かは13章で明かされる

2.2ハードウェアのセグメント機構
80286以降のインテルプロセッサにはリアルモードとプロテクトモードの二種類のアドレス変換方式がある。
リアルモードは今はあまり使われない。ここではプロテクトモードにフォーカスする。リアルモードについては付録で詳述

論理アドレスはセグメント識別子とセグメント内の相対アドレスを示すオフセットで構成される。
セグメント識別子とはセグメントセレクタと呼ばれる16ビットのフィールド。オフセットは32ビットのフィールド。

プロセッサはセグメントセレクタを保持するためのセグメントレジスタを用意している
種類はcd,ss,ds,es,fs,gsがある
cs:プログラム命令が置かれたセグメントを指すコードセグメントレジスタ
  うち2ビットはCPUの現行特権レベルことCurrent Praiviledge Levelを示す。0なら最高、3なら最低。Linuxでは0と3だけ使う。0ならkernelモード、3ならユーザモード。
ss:現在実行しているプログラムのスタックを示すスタックセグメントレジスタ
ds:グローバルな静的データが置かれたセグメントを指すデータセグメントレジスタ

2.2.2セグメントディスクリプタ
セグメントの特性を表現するのがセグメントディスクリプタ
グローバルディスクリプタテーブルかローカルディスクリプタテーブルに置かれる

データセグメントディスクリプタ

63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32
ベース               G B O AVL リミット   1 DPL S=1 タイプ       ベース            
ベース                             リミット                            
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0

コードセグメントディスクリプタ

63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32
ベース               G D O AVL リミット   1 DPL S=1 タイプ       ベース            
ベース                             リミット                            
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1

0

システムセグメントディスクリプタ

63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32
ベース               G   O   リミット   1 DPL S=0 タイプ       ベース            
ベース                             リミット                            
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0

ベース:セグメント先頭のリニアアドレスを示す

G:セグメント長をバイト長で示すなら0、ページ単位なら1

リミット:セグメントの終端のメモリセルのオフセット。Gフラグが0なら1バイト-1Mバイト、1なら4KB-4GB

S:システムフラグ。0ならばLDTなどの重要なデータ構造を置くシステムセグメント、1ならば通常のコードセグメントかデータセグメント

タイプ:セグメントの種類とアクセス権を示す

DPL:セグメントへのアクセスを制限するためのDPL

P:セグメント存在フラグ。セグメントがメインメモリ上に存在していない場合は0。Linuxではセグメント全体をディスクにスワップアウトすることはないので常に1。

D or B:セグメントをコード用に使っているのか、データ用に使っているのかによって意味が変わるフラグ。基本はセグメントオフセットが32ビット長なら1、16ビットなら0。

AVL:Linuxでは使わない

2.2.3高速アクセス

セグメントセレクタ→セグメントディスクリプタと紐づく

セグメントレジスタがセグメントセレクタを読み込むときにノンプログラマブルレジスタにセグメントディスクリプタをロードすることで論理アドレス変換時にGDTないしLDTまでアクセスしに行く必要がなくなる

 

2.2.4セグメンテーション回路

セグメンテーション回路は次の手順でリニアアドレスへ変換する

①セグメントセレクタのT1フィールドがGDTならgdtrレジスタ、LDTならldtrレジスタからベースリニアアドレスを取得

②セグメントセレクタのインデックスフィールドからセグメントディスクリプタのアドレスを計算する。インデックスフィールドを8倍してgdtr/ldtrレジスタの値を加える

③セグメントディスクリプタのベースフィールドに論理アドレスのオフセットを加えてリニアアドレスを得る

2.3Linuxのセグメント機構

Linuxでは原則ページング機構のみを用いる

ユーザモードで動作するすべてのLinuxプロセスは一組の命令セグメント=ユーザコードセグメントとデータセグメント=ユーザデータセグメントを使用する

他方、カーネルモードで動作するすべてのLinuxプロセスは一組の命令セグメント=カーネルコードセグメントとデータセグメント=カーネルデータセグメントを使用する。

セグメント ベース G リミット S タイプ DPL D/B P
ユーザコード 0x00000000 1 0xfffff 1 10 3 1 1
ユーザデータ 0x00000000 1 0xfffff 1 2 3 1 1
kernelコード 0x00000000 1 0xfffff 1 10 0 1 1
kernelデータ 0x00000000 1 0xfffff 1 2 0 1 1

リニアアドレスの範囲がすべて同じなのでユーザモードでもカーネルモードでも同じ論理アドレスを使用することが可能である

また、Linuxのセグメントは全て同じ値から始まるので、常に論理アドレスのオフセットフィールドがリニアアドレスと一致する。

csレジスタCPLが変わる場合、対応する他のレジスタのセグメントセレクタも変えないとだめ→dsはユーザデータとカーネルデータの間で入れ替え、ssは参照先のスタックをユーザモードとカーネルモードで入れ替え

以上の仕組みにより、、

データ構造へのポインタを退避する場合は論理アドレス空間を示すセグメントセレクタを保存する必要はない。→ssレジスタが現在のセグメントセレクタを保存しているため

カーネルが関数を呼び出す場合はアセンブリのcallを実行するが、論理アドレスのオフセット部分のみを指定する→論理アドレスのセグメントセレクタはcsレジスタによって暗黙的に指定される→カーネルモードで実行可能なセグメントが1つしかないため

2.3.1LinuxのGDT

CPUは1つずつGDTを用意する

GDTのアドレスと大きさはcpu_gdt_table配列に置かれる

null segment 0x0 TSS 0x80
reserved   LDT 0x88
reserved   PNPBIOS 32bit code 0x90
reserved   PNPBIOS 16bit code 0x98
not use   PNPBIOS 16bit data 0xa0
not use   PNPBIOS 16bit data 0xa8
TLS #1 0x33 PNPBIOS 16bit data 0xb0
TLS #2 0x3b APM BIOS 32bit code 0xb8
TLS #3 0x43 APM BIOS 16bit code 0xc0
reserved   not use  
reserved   not use  
reserved   not use  
kernel code 0x60(__KERNEL_CS) not use  
kernel data 0x68(__KERNEL_DS) not use  
user code 0x73(__USER_CS) not use  
user data 0x7b(__USER_DS) double fault TSS 0xf8

TSS=task state segment

TSSって何かは3章で詳述される

LDT=LDT管理セグメント。通常全プロセスがこのセグメントを共有する。

TLS=Thread Local Storeageマルチスレッドアプリケーションにおいてスレッド固有データを保持するセグメントを最大3つまで使える機構。set_thread_areaでTLSセグメントを作成、get_thread_areaでTLSセグメントを解放

APM=アドバンスドパワーマネジメント用のコードセグメントとデータセグメント

プラグアンドプレイ用のセグメントpnp

カーネルダブルフォルト例外を処理するための特別なTSSセグメント。4章で詳述

システム内のプロセッサは以下以外は同一のエントリを保持する

プロセッサ固有のTSSセグメント、プロセスに依存するLDTとTLSのセグメントディスクリプタ、プロセッサが一時的にエントリを変更する場合

2.3.2LinuxのLDT

ユーザモードアプリケーションの殆どはLDTを使用しない。

カーネルが定義した標準初期設定のLDTを共有する

2.4ハードウェアページング機構

ページング回路はリニアアドレスを物理アドレスへ変換する

リニアアドレスはページという一定の大きさの領域に分けられる

ページはリニアアドレスそのものないし、その内容を指して使われる

ページング回路はすべてのRAMを固定サイズで分割したページフレームの集まりとみなす。これを物理ページと呼んだりもする。

ページフレームはメインメモリの構成要素、つまり記憶領域、ページは任意のページフレーム内やディスク上に置くことのできるデータの集まり。両者は明確な区別が必要。

リニアアドレスを物理アドレスマッピングするデータ構造をページテーブルと呼ぶ

2.4.1通常のページング

ページング回路が扱うのは4KBページ。

32ビット長のリニアアドレスは3つのフィールドに分けられる。

ディレクトリ:上位10ビット

テーブル:中間の10ビット

オフセット:下位12ビット

 リニアアドレス変換は二段階、ページディレクトリ→ページテーブルの順で変換する

目的はページテーブルのRAM量を減らすため。→もし一段階しかなかった場合、プロセスが4GBのメモリアドレスを使用した場合プロセスごとに2の20乗のエントリが必要になる=1ページテーブルエントリの大きさを4バイトとすれば4MB使うことになる→二段階変換であればプロセスが実際に使用している仮想記憶領域のページテーブルだけで済む

使用中のページディレクトリの物理アドレスはcr3制御レジスタにある。リニアアドレスのディレクトリフィールドはページディレクトリ内のエントリを指し、このエントリがページテーブルを指す。次に、リニアアドレスのテーブルフィールドがページテーブル内のエントリを指し、このエントリにページを含むページフレームの物理アドレスがある。

ページディレクトリとページテーブルのエントリは同じ構造をしていて、次のフィールドがある

・存在フラグ:1なら参照先のページないしページテーブルはメインメモリに存在する。ページング回路は変換に失敗したリニアアドレスをcr2制御レジスタに保存して、ページフォルト例外を発生させる

・ページフレーム物理アドレスの上位20ビットを持つフィールド:このフィールドがページディレクトリ内にある場合は参照先のページフレームにはページテーブルが有る。ページテーブル内にある場合には参照先のページフレームにはデータのページが有る。

・アクセス済みフラグ:対応するページフレームにアドレッシングするとページング回路はこれを1にする。OSがスワップアウトするページを選ぶときに参考にする。0にするのはOSの役割。

・ダーティフラグ:ページテーブルエントリの場合だけ使用。ページフレームに対する書き込み処理が行われるたびに1に設定する。OSがスワップアウトするページを選ぶときに参考にする。0にするのはOSの役割。

・Read・Writeフラグ:ページまたはページテーブルのアクセス権を示す

・ユーザ・スーパバイザフラグ:ページまたはページテーブルアクセスに必要な特権フラグ

PCDフラグとPWTフラグ:ハードウェアキャッシュがページまたはページテーブルを処理する方法を制御

・ページ長フラグ:ページディレクトリエントリの場合のみ使用する。エントリが2MBまたは4MBのページフレームを参照する場合はこのフラグを1にする

・グローバルフラグ:ページテーブルエントリの場合だけ使用する。Pentinum Proから導入されたフラグで、頻繁に使用するページがTLBから追い出されるのを防ぐ。この機能は制御レジスタcr4のページグローバルイネーブルフラグを1に設定したときのみ有効になる。

 

・グローバルフラグ:ページテーブルエントリの場合だけ使用する。Pentinum Proから導入されたフラグで、頻繁に使用するページがTLBから追い出されるのを防ぐ。この機能は制御レジスタcr4のページグローバルイネーブルフラグを1に設定したときのみ有効になる。

 

2.4拡張ページング

ページフレームの大きさを4kbから4mbにする機能

広範囲の連続したリニアアドレスを物理アドレスに変換する際に使用する

 

2.4.3 ハードウェア保護機構

80x86プロセッサではセグメントに対して4種類の特権レベルがあるのに対して、ページとページテーブルに対しては2種類しかない→ページとページテーブルの特権レベルはユーザ・スーパバイザフラグで制御するため

セグメントはread, write, excuteの権限制御があるが、ページにはread, writeしかない

#2.4.5物理アドレス拡張ページング機構ー2.4.6 64ビットアーキテクチャにおけるページングは頭が痛くなってきたので後日読み返す

2.4.7ハードウェアキャッシュ

プロセッサのクロック数に対してDRAMのIOは遅すぎなのでCPUとDRAMの速度の差を縮める必要あり→SRAMを間に噛ませる。これをラインという。

プロセッサはそれぞれキャッシュを持つので、同期をとる必要がある。その仕組みをキャッシュスヌーピングという。

最近のプロセッサはL1キャッシュだけでなくL2,L3キャッシュもあり、数字が大きくなるほどサイズがでかいが速度が遅い。Linux視点ではこれらの詳細は無視して1つのキャッシュとみなす。

2.4.8 アドレス変換バッファ(TLB)

リニアアドレスを高速変換するためのバッファ。メモリではなくプロセッサのキャッシュにページテーブルを保持する。

#残りはおいおい咀嚼しながら追記