#概要#
まずはメモリアドレッシングについてです。
現在、linuxではセグメント機構は一部でしか使用されていませんが
まぁ投稿します。
本を読みながらなので本の引用が多くなるかもしれませんが
ご了承ください。
詳しく用語の説明等はしないので(今回の目的と違う)
簡単な説明、参考サイト等を記載しておきます。
#知っておいたほうがいい用語#
セグメント
セグメントレジスタ
GDT(グローバルディスクリプタテーブル)、LDT(ローカルディスクリプタテーブル)
#メモリアドレス#
まず、x86プロセッサではメモリ空間を3種類に分けます。
- 論理アドレス
セグメントセレクタ(後で説明します)とオフセットで構成されています。
オフセットはセグメントの先頭からどれぐらいの距離(アドレス)にいるかを表しています。
- リニアアドレス(仮想アドレス)
4G(4,294,967,296個)のメモリセルをしていきでき、32ビット符号なし整数で表現します。
なぜ4GというとOSの問題とハードウェア(特にマザボ)の問題があります。以下省略します。
詳しく知りたい方は参考サイトを記載しておきます。
32bit/64bitパソコンでメモリが3GBしか認識しない問題
- 物理アドレス
メモリチップ内のメモリセルを指定します。つまり言葉のまま、物理的なアドレスの指定に使用します。
これら3種類のアドレスを
論理アドレス
↓セグメンテーション回路(メモリ管理回路 MMU)
リニアアドレス
↓ページング回路
物理アドレス
の順に変換して行き目的のアドレスにアクセスします。
- 参考サイト
##セグメント##
###論理アドレス###
論理アドレスを構成しているのはセグメントセレクタ(セグメント識別子)(16bit)とオフセット(32bit)です。
この2つの要素を使用してリニアアドレスを導きます。
セグメントセレクタの構造
15----------------3- 2-1-0
| インデックス |TI|RPL|
インデックス( 13bit )
--ディスクリプタテーブル内の対応するセグメントディスクリプタを指定
TL ( テーブルインジケータ Table Indicator ) フラグ( 1bit )
--セグメントディスクリプタがGDT(TI=0)かLDT(TI=1)かのフラグ
RPL (Requestor Privilege Level) フィールド ( 2bit )
--リクエスト特権レベルフィールド。対応するセグメントセレクタをCSレジスタに読み込んだときのCPUの実行特権レベルを表す。
CSレジスタの機能としてCPUの現行特権レベルを表す2bitがあります。
なのでこの2bitを参照し特権レベルを調べます。
例)0のとき 最も高い特権レベル(カーネルレベル) 3のとき 最も低い特権レベル(ユーザモード)
セグメントセレクタを格納するレジスタをセグメントレジスタといい、
このセグメントセレクタを使用してセグメントディスクリプタを取得します。
###セグメントディスクリプタ###
各セグメントの開始位置、セグメントのサイズアクセス制限などセグメントごとの特性を表しています。
セグメントディスクリプタの構造体
//version 2.6
//linux/arch/x86/include/asm/desc_defs.h 22行目
/*8 byte segment descriptor */
struct desc_struct {
union{
struct {
unsigned int a; //下位32bit 0-31
unsigned int b; //上位32bit 32-63
};
struct {
u16 limit0; //unsigned shortと同じbit数の型宣言
u16 base0; //unsigned shortと同じbit数の型宣言
unsigned base1: 8, type: 4, s: 1, dpl: 2, p: 1;
unsigned limit: 4, avl: 1, l: 1, d: 1, g: 1, base2: 8;
};
}
}
//version 3.18
//Linux/arch/x86/include/asm/desc_defs.h 22行目
struct desc_struct {
union {
struct {
unsigned int a;
unsigned int b;
};
struct {
u16 limit0;
u16 base0;
unsigned base1: 8, type: 4, s: 1, dpl: 2, p:1 ;
unsigned limit: 4, avl: 1,l: 1,d: 1,g: 1,base2: 8;
};
};
}__attribute__((packed));
/*
v2.6とv3.18ではメンバは一緒なのですがv3.18では__attribute__((packed))が付いています
これは構造体に詰め物をせず、純粋にメンバのみで構成させるようにします。
*/
desc_struct構造体は64bitで構成されています。
desc_struct構造体のメンバについて説明します(カッコ内は構造体のメンバ名 or 名前)
ベース( base2 [24-31bit],base1[16-23bit],base0[0-15bit] ) 32bit
--セグメントの先頭のリニアアドレス
G ( グラニュラリティGranularity ) ( g ) 1bit
--セグメント長の表し方。バイト単位の場合は 0 ,ページ単位の場合は 1
リミット( limit [16-19bit],limit0 [0-15bit] ) 20bit
--セグメント長 Gフラグが0の場合は1バイトから1MBの範囲 Gフラグが1の場合は4KBから4GBの範囲
S ( システムフラグ )( s ) 1bit
--0の場合このセグメントはGDTなどの重要なデータ構造を置く 1の場合通常のコードセグメントかデータセグメント
タイプ( type ) 4bit
--セグメントの種類とアクセス権を表す(詳細は後ほど)
DPL(ディスクリプタ特権レベル Descriptor Privilege Level)( dpl ) 2bit
--セグメントへのアクセス制限をする。0の場合 カーネルモード 3の場合 ユーザモード
P(セグメント存在(Segment-Present)フラグ)( p ) 1bit
--セグメントが現在のメモリ上に存在していない場合は0を設定
--ただし、セグメントをディスクにスワップすることはないのでこのフラグ(bit 47)は常に1
D( d ) 1bit
--セグメントがコード用かデータ用かで異なる。
--基本的な意味はセグメントオフセットが32bitの場合は1 16bitの場合は0
AVLフラグ( avl ) 1bit
--linuxでは使用しないので省略します
- 参考サイト
x86_64のセグメントディスクリプタ(主にGDTで確認)
Linuxメモリー・モデルの探究
プロテクトモードのセグメント機構
###セグメントテーブル###
GDT
各プロセッサ固有のディスクリプタテーブルです。
ここでcpu_gdt_table配列なのですがv2.6.31では実装してないみたいです(自分が見てるソースコードバージョン)
自分調べですが、v2.6.25.8までは実装しているみたいです。
ちなみにディレクトリは linux/include/asm-x86/desc.h 33行目
extern struct desc_struct cpu_gdt_table[GDT_ENTRIES];
GDT_ENTRIES これはGDTのエントリー数を示すマクロ linux/include/asm-x86/segment.h
このマクロはkernelのビット数に応じて変化し、
32bitの場合は 32
64bitの場合は 16 (linux/include/asm-x86/segment.hを参照)
cpu_gdt_tableはcpu_gdt_descr配列に置かれます
// linux/arch/x86/kernel/setup64.c 280行目
//各CPUごとに初期化処理をします
if(cpu)
memcpy(get_cpu_gdt_table(cpu),cpu_gdt_table,GDT_SIZE);
/*
//version 2.6
#define GDT_SIZE (GDT_ENTRIES * 8)
get_cpu_gdt_table(cpu)は /include/asm-x86/desc.hで定義されています。
#define get_cpu_gdt_table(x) ((struct desc_struct *)cpu_gdt_descr[x].address)
引数のCPUのGDTをcpu_gdt_tableをコピーして初期化しています。
ちなみに、v3.18でのget_cpu_gdt_tableの定義は
//version 3.18
// arch/x86/include/asm/desc.h 48行目
static inline struct desc_struct *get_cpu_gdt_table(unsigned int cpu){
return per_cpu(gdt_page,cpu).gdt ;
}
*/
GDT内の各セグメントの説明は省略します。
LDT
ユーザモードアプリケーションではほとんど使用せず、プロセスはカーネルが定義した
標準初期設定のLDTを共有します。
default_ldt配列内にLDTはおかれるのですが、
version2.6.11.8以降は実装されていないようです(自分調べ)。
linux/arch/i386/kernel/traps.c 61行目
linux/include/asm-i386/desc.h 33行目
以上がセグメントについての説明です。
現在はセグメントをほとんど使用していないので最新のKernelでは実装されていないものが
あります。
昔のKernelを触るときやセグメントを使用しているKernelを使うときに参考に
していください。
次はページング機構について説明します。
PS.自分調べのところはCross-Referenced Linux and Device Driver Codeに記載されていたKernelのみしか調べていませんので詳細がわかりしだい、更新します。