|
0から作るOS開発 カーネルローダその2 プロテクティッドモードとGDT
|
前回までの内容
これまでで、
- リアルモードで使用できるメモリマップから0x00000500から0x000A0000が未使用領域
- メモリマップを見ると、リアルモードでは読み込めるファイルのサイズに限界がある(638kバイト)
- 大きなサイズのカーネルを読み込むには32ビットで動作するプロテクティッドモードで読み込む必要がある
- 1段階目のブートローダは512バイトと小さいため、2段階目のブートローダを作る必要がある
- 一般的なOSは0x00100000にカーネルがロードされる
ということがわかりました。それではカーネルローダの開発を進めていきましょう!
前回までで、カーネルローダに移行することができました
今回は主に32ビットで動作するプロテクティッドモードについて見ていきます
最初は32ビットのレジスタについて説明します
32ビットレジスタ
今までは16ビットのレジスタのみを紹介してきました(EFLAGSレジスタは32ビットでした)
32ビットのプロテクティッドモードにする前に、32ビットのレジスタについて紹介します
16ビットの汎用レジスタAX、BX、CX、DX、BP、SI、DI、SPは拡張されて(Extended)
それぞくれEAX、EBX、ECX、EDX、EBP、ESI、EDI、ESPの名前で32ビットのレジスタとして
使用できます。また、命令ポインタIPもEIPとして32ビットのレジスタとして使用できます
(もちろんAX、AHやALなども同時に使用できます)
それでは本題のプロテクティッドモードについて見ていきたいと思います
プロテクティッドモード
プロテクティッドモード(Protected Mode)は仮想メモリが扱えますので、
その名前の通り”保護モード”と言えます。
プロテクティッドモードの特徴として
- 32ビットで動作する
- 仮想メモリ(ページング)が使用できる
- 仮想メモリを扱うため、メモリを扱う方法がリアルモードから変わる
- 32ビットで動作するためメモリを4GBまで使用することができる
- BIOSの処理が使用できなくなる
などがあります。特にリアルモードで使用してきましたセグメントレジスタとその使いかたが
劇的に変わってしますので、注意が必要です
今回はプロテクティッドモードへの移行する手順について見ていきます
プロテクティッドモードへの移行
プロテクティッドモードへ移行し、4GBのメモリ空間を利用するには次の手続きを踏む必要があります
- プロテクティッドモード用のメモリアクセス設定を行う
- プロテクティッドモードをONする
- 4GBのメモリ空間を利用できるようにする
この3段階の設定を行う必要があります
プロテクティッドモード用のメモリアクセス設定はGDT(Global Descriptor Table)を使った
メモリアクセスを行う準備をします。次のプロテクティッドモードをONするにはCPUの
特殊なレジスタであるコントロールレジスタCR0の設定を行います
そして、4GBののメモリ空間を利用するにはCPUのアドレスバスを使用出来るようにキーボードコントローラから設定します
またまた、いきなり難易度が急上昇しました。難しい語句がいきなりでてきて何のことやらさっぱりです。。。
かなり難しいので、順番に詳細を見ていきたいと思います。まずはプロテクティッドモード用のメモリアクセス設定を行う方法です
プロテクティッド用のメモリアクセス設定 GDT(Global Descriptor Table)
リアルモードではコードセグメントCS、データセグメントDS、FS、ES、スタックセグメントSSと各セグメントに対応したオフセットを
使ってメモリアクセスしていました。プロテクティッドモードでは上記のセグメントレジスタとGDTを使ってメモリにアクセスします
ではGDTについてみていきましょう
GDT(Global Descriptor Table)
GDTを日本語で言うと”大域的記述子表”といったところでしょうか。日本語にするとますます難しい。。。ですが
Tableというその名の通りテーブルで、GDTは64ビットを1つのデータとした配列(テーブル)となります
そして1つの要素である64ビットのデータはセグメントディスクリプタ(Segment Descriptor)といい、
セグメントの開始位置、セグメントのサイズアクセス権限の設定などを細かく設定できます
このセグメントディスクリプタは全部で8192個まで持つことができます
プロテクティッドモードでは仮想メモリとしてセグメンテーションが扱えるように
GDTが作られています。逆にGDTを巧みに駆使することでセグメンテーションを行います
LinuxなどのOSは仮想メモリとしてセグメンテーションではなく管理が比較的楽なページングを使っていますが、パソコンについては
CPUの仕様上セグメンテーションの設定を行う必要があります。必要ありますが、Linuxでは4GBのメモリ空間全部を
1つのセグメントとしてみなしてGDTの設定をしています。このホームページでもその方針でいきます
GDTとセグメントレジスタの役割
GDTはセグメントディスクリプタのテーブルで、各セグメントディスクリプタはメモリ上のセグメントに対応しています
その対応したセグメントの開始位置、サイズ、アクセス権限の設定をGDTのセグメントディスクリプタで行うことができます
リアルモードでは各セグメントの開始位置はCS、DS、ES、GS、FS、SSで決定していましたが、
このようにプロテクティッドモードではGDTのセグメントディスクリプタで決定するため
リアルモードで使用していたCS、DS、ES、FS、GS、SSは違う役割となります
プロテクティッドモードのセグメントレジスタCS、DS、ES、FS、GS、SSの役割はGDTの何番目の
セグメントディスクリプタを使用するかを決めます
図で見て行きましょう
このようにセグメントレジスタはGDTのセグメントディスクリプタの何番目を使用するのかを決めます
何番目かを指定するときにはセグメントディスクリプタのオフセット番号を指定します
例えば図のCSは1番目(最初を0番目として)を指しています
セグメントディスクリプタのサイズは64ビットで8バイトですので、1番目のセグメントディスクリプタの
開始位置は8バイト目となります。ですので、CSは0x08を入れると1番目のセグメントディスクリプタを指すことになります
同様にDSには0x10が入っています。2番目のセグメントディスクリプタの開始位置は16バイト目ですので
DSに0x10(16)を入れることで2番目のセグメントディスクリプタを指すことになります
このようにセグメントレジスタの値はGDTのセグメントディスクリプタを選択する役割となりますので、
セグメントセレクタと呼ばれます
論理アドレスとリニアアドレス
リアルモードではセグメントレジスタの値:オフセットの論理アドレス(Logical Address)でメモリにアクセスしました
論理アドレスを計算したアドレスがリニアアドレス(Linear Address)でした。同様にプロテクティッドモードでも
[セグメントセレクタの値:オフセット]が論理アドレスとなります
ただしセグメントセレクタの値はリアルモードと同様16ビットとなりますが、オフセットは32ビットとなります
このように、例えばプログラムでアドレス0x00001000の場所にアクセスする場合
- 論理アドレスのセグメントレジスタに格納されているセグメントセレクタを見る
- セグメントセレクタは0x08なので1番目のセグメントディスクリプタを見る
- セグメントディスクリプタのベースアドレスが0x00000500であるので、この値と
論理アドレスのオフセット(プログラムにはこのオフセットの値をアドレスとして書きます)
0x00001000を足して0x00001500を計算してリニアアドレスに変換してアクセスする
となります。リニアアドレスに変換する作業はCPUが勝手に計算してくれます
ここで注意していただきたいのですが、プログラム中ではオフセットであるアドレスだけを主に書いています
論理アドレスはセグメントレジスタの値とアドレス(オフセット)の組み合わせですので、
プログラム中に明示的にセグメントレジスタを指定しなければ、CPUはどのセグメントセレクタを
使っていいのかわかりません。ですが、主に使うセレクタはCS(コード)、DS(データ)、SS(スタック)
だけですので、プログラムでは特にどのセグメントセレクタを使うのかを書く必要はありません
例えばプログラムを実行するのなら決まってCSですし、データの読み書きはDSを使います
POP、PUSH命令であれば自動的にSSのセレクタを使うので、プログラムにはアドレス(オフセット)だけを書きます
ES、FS、GSのセグメントセレクタを意図的に使いわける場合は明示的に指定する必要があります
(またはES、FS、GSレジスタを使う命令を使うときには意識して使う必要があります)
セグメントレジスタのセグメントセレクタ
プロテクティッドモードはメモリを保護するモードでメモリにアクセスする権限を設定できます
アクセス権限にはレベルがあり、特権レベルと呼ばれます。特権レベルは0から3までのレベルをとります
とくにカーネルでは特権レベルはリングと呼ばれますので、リングはリング0からリング3まであります
リングはリング0が最も特権レベルが高く、リング3が最も低い特権レベルとなります
LinuxなどのOSではカーネルにリング0の特権レベルが与えられ、ユーザアプリケーションはリング3が与えられています
特にリング1、2の特権レベルは使用されておらず、リング0とリング3が使われています
セグメントにアクセスするときにリングレベルによってアクセスできるかどうかが決まります
そのセグメントにアクセスできるリングレベルのこを必要な特権レベル(RPL:Requested Privilege Level)といいます
RPLはセグメントセレクタの値で設定されます。それではセグメントセレクタのビットアサインを見ていきましょう
セグメントセレクタ
|
ビット
|
ビット名称
|
説明
|
0-1
|
RPL
|
そのセグメントアクセスに要求される特権レベル
0が最高位のレベル、3が最低のレベル
|
2
|
TI
|
テーブルインジケータ
0:GDT
1:LDT(Local Descriptor Table)
※LDTはローカルディスクリプタテーブルといいます。GDTと同じようにセグメントディスクリプタを持っています
|
3-15
|
Index
|
GDTのインデックス
インデックスはGDTの何番目のディスクリプタを指しているかを示す値です
インデックスにはGDTのディスクリプタ番号×8を計算した値をいれてください
|
セグメントレジスタにセグメントセレクタの値を設定するにま今までどおりMOV命令を使用します(例外もあります)
例えばリング0でGDTの1番目のセグメントディスクリプタが示すコードセグメントを使用するときは
MOV CS, 0x08
とします。またリング3で同じくコードセグメントを使用するときは
MOV CS, 0x0B
としますが、特権レベルを切り替えるときはセグメントにアクセス権があるかどうかの注意が必要となります
セグメントディスクリプタ
セグメントディスクリプタをセグメントセレクタで指定することができるようになりました
次に、セグメントセレクタが指定するGDT、LDT(セグメントセレクタの表で少しでてきました
今は関係ありませんので、こんなものがあるんだぐらいで)のセグメントディスクリプタについて見ていきます
セグメントディスクリプタ
|
ビット
|
ビット名称
|
説明
|
0-15
|
Segment Limit Low
|
そのセグメントのサイズを入れます。セグメントリミットは全部で20ビットのサイズですが
ここは全20ビットのうち下位16ビット(0番目のビットから15番目のビット)を入れます
|
16-31
|
Base Address Low
|
そのセグメントの開始アドレスを入れます。ベースアドレスは全部で32ビットのサイズですが
ここは全32ビットのうち下位16ビット(0番目のビットから15番目のビット)を入れます
|
32-39
|
Base Address Mid
|
そのセグメントの開始アドレスを入れます。ベースアドレスは全部で32ビットのサイズですが
ここは全32ビットのうち中位8ビット(16番目のビットから23番目のビット)を入れます
|
40-43
|
Type
|
セグメントのタイプを設定します。Sビットが1の場合ビット43でコードセグメントかデータセグメントか設定できます。
ビット40 アクセスビット
0:未アクセス
1:アクセス済み
仮想記憶で使用します。そのセグメントにアクセスしたかどうかをセットします
ビット41 リード/ライトビット(データセグメントの場合)
0:読み取り専用
1:読み取り/書き込み可能
リード・ライトのアクセス設定ができます
ビット41 リード/実行ビット(コードセグメントの場合)
0:実行専用
1:読み取り/実行可能
リード・実行のアクセス設定ができます
ビット42 伸長方向ビット(データセグメントの場合)
0:アップ方向
1:ダウン方向
セグメントがメモリの上、下どちらの方向に伸びているかを設定します。
通常はアップ方向です。
ビット42 コンフォーミングビット(コードセグメントの場合)
0:非コンフォーミング
1:コンフォーミング許可
コンフォーミング許可(1)の場合には、低い特権レベルのプログラムから
このセグメントのコードを実行することができます。
非コンフォーミング(0)の場合は、逆に低い特権レベルからはこの
セグメントのコードを実行することができませんので、カーネルのコードは
この設定を行います。
ビット43 セグメント
0:データセグメント
1:コードセグメント
セグメントがデータセグメントなのかコードセグメントなのかを設定します
|
44
|
S
|
ディスクリプタタイプフラグです
0:システムセグメント用
1:コードセグメント、データセグメント用
|
45-46
|
DPL
|
Descriptor Plivilege Levelです。セグメントの特権レベルを設定します
0は最高位の特権レベル。リング0。0から3の値で指定します
セグメントセレクタで指定するRPLのレベルは現在実行中のレベルを意味します
セグメントにアクセスする場合はセグメントセレクタのRPLとこのDPLビットが比較されます
|
47
|
P
|
Segment Present Flagです。セグメントがメモリ内に存在しているかどうかを設定します
0:セグメントがメモリ内に無い
1:セグメントがメモリ内に有る
通常はメモリ内にセグメントがありますので、1をセットします
|
48-51
|
Segment Limit Hi
|
そのセグメントのサイズを入れます。セグメントリミットは全部で20ビットのサイズですが
ここは全20ビットのうち上位4ビット(16番目のビットから19番目のビット)を入れます
|
52
|
AVL
|
Availableフラグです。このビットについてはOS(カーネル)が自由に使用してよいビットとなります
使用目的は自由です
|
54
|
D/B
|
デフォルトのオペレーションサイズを設定します
0:16ビット用のコードセグメント、データセグメントに使用します
1:32ビット用のコードセグメント、データセグメントに使用します
|
55
|
G
|
Granularity Flag(グラニュラリティフラグ)です。セグメントリミットの単位を設定します
0:セグメントリミットの値をバイト単位として解釈します
1:セグメントリミットの値を×4Kバイト単位として解釈します
通常は1に設定します
|
56-63
|
Base Address Hi
|
そのセグメントの開始アドレスを入れます。ベースアドレスは全部で32ビットのサイズですが
ここは全32ビットのうち上位8ビット(24番目のビットから31番目のビット)を入れます
|
各セグメントのセグメントディスクリプタを作成してGDTを作っていきます
GDTの作成とロード
GDTのデータをCPUに教える命令がLGDT命令です。LGDT命令ではGDTのサイズとアドレスを格納している
アドレスを指定します。GDTのサイズとアドレスを格納しているデータを見てみましょう
gdt_toc: ; GDTとサイズとアドレスを格納しているアドレスラベル
DW 8*4 ; GDTのサイズ
DD _gdt ; GDTのアドレス
この例ではgdt_tocにGDTのサイズとアドレスを格納しています。GDTのセグメントディスクリプタは64ビットで8バイト
ですので、今回はセグメントディスクリプタを4つ作りますので8バイト×4としました
サイズは16ビットで、アドレスは32ビットの大きさで格納しておきます
そしてgdt_tocをLGDTで読み込みます
LGDT [gdt_toc] ; gdt_tocのアドレスを指定して読み込む
読み込む前にGDTのテーブルを作っておきます
_gdt: ; このラベルをgdt_tocに登録する
; Null descriptor
DW 0x0000 ; 全部値が0のNullディスクリプタを作ります
DW 0x0000 ; Intel CPUの仕様で決まっています
DW 0x0000
DW 0x0000
; Code descriptor
DB 0xFF ; コードセグメントのサイズ(Segment Limit)を入れます
DB 0xFF ; サイズは0xFFFFFを入れます
DW 0x0000 ; コードセグメントのベースアドレスを入れます。0x00000000を入れます
DB 0x00 ; Base Address Mid
DB 10011010b ; Type、S、DPL、Pの値入れます
DB 11001111b ; Segment Limit Hi、AVL、0、D/B、Gの値を入れます
DB 0 ; Base Address Hi
; Data descriptor
DB 0xFF ; データセグメントのサイズ(Segment Limit)を入れます
DB 0xFF ; サイズは0xFFFFFを入れます
DW 0x0000 ; データセグメントのベースアドレスを入れます。0x00000000を入れます
DB 0x00 ; Base Address Mid
DB 10010010b ; Type、S、DPL、Pの値入れます
DB 11001111b ; Segment Limit Hi、AVL、0、D/B、Gの値を入れます
DB 0 ; Base Address Hi
; TEMPORARY
DW 0x0000
DW 0x0000
DW 0x0000
DW 0x0000
GDTでセットアップする必要がある必須のセグメントディスクリプタとして3つのディスクリプタがあります
(例ではテンポラリを作っていますが特にいりません)
- Nullディスクリプタ
- コードセグメントディスクリプタ
- データセグメントディスクリプタ
この3つのセグメントディスクリプタについてみていきます
Nullディスクリプタ
Intelの仕様では0番目のセグメントディスクリプタとして値が全部0のNullディスクリプタを作る必要があります
; Null descriptor
DW 0x0000 ; 全部値が0のNullディスクリプタを作ります
DW 0x0000 ; Intel CPUの仕様で決まっています
DW 0x0000
DW 0x0000
値を0で全部埋めます。セグメントディスクリプタは64ビットで8バイトであることに注意してください
コードセグメントディスクリプタ
特にプログラムを置くセグメントの設定をするディスクリプタとなります
; Code descriptor
DB 0xFF ; コードセグメントのサイズ(Segment Limit)を入れます
DB 0xFF ; サイズは0xFFFFFを入れます
DW 0x0000 ; コードセグメントのベースアドレスを入れます。0x00000000を入れます
DB 0x00 ; Base Address Mid
DB 10011010b ; Type、S、DPL、Pの値入れます
DB 11001111b ; Segment Limit Hi、AVL、0、D/B、Gの値を入れます
DB 0 ; Base Address Hi
GDTは難しいので一つずつ見ていきたいと思います
DB 0xFF ; コードセグメントのサイズ(Segment Limit)を入れます
DB 0xFF ; サイズは0xFFFFFを入れます
最初の0ビット目から15ビット目はSegment Limit Lowです
Segment Limitはセグメントのサイズを決めます。セグメントのサイズは55ビット目のGビットも関係します
通常32ビット仮想記憶は4GBまで扱えますので、Gビットを1にしてSegment Limitの単位を4Kバイトに
します。この例ではSegment Limitのサイズは0xFFFFにしていますので、
0xFFFFF×4Kバイト=0xFFFFF000がセグメントのサイズとなります
LinuxなどのOSでは主にページングを使用しますが、Intel CPUの場合は使用上セグメンテーションの
設定が必須となっています。そこのでセグメンテーションを意識することなく使用するために、
仮想メモリ全体を1つのセグメントとして設定しています。この例でも同様にしていて、
ベースアドレス0x00000000でサイズ0xFFFFF000の大きな1つのセグメントとして設定を行います
(この例ではDBを2つ置いてますがDWを1つのほうが見やすいかと思います)
DW 0x0000 ; コードセグメントのベースアドレスを入れます。0x00000000を入れます
DB 0x00 ; Base Address Mid
16ビット目から31ビット目はBase Address Lowで32ビット目から39ビット目はBase Address Mideです
ベースアドレスは0x00000000にしたいので両方0x00で埋めます
DB 10011010b ; Type、S、DPL、Pの値入れます
次は40ビット目から47ビット目までの設定を行なっています
- 40ビット目:アクセスビット:0:未アクセスをセットしています
- 41ビット目:リード/ライトビット:1:読み取り/書き込み可能をセットしています
- 42ビット目:伸長方向:0:アップ方向にセットしています
- 43ビット目:セグメント:1:コードセグメントにセットしています
- 44ビット目:Sビット:1:コードセグメント、データセグメント用にセットしています
- 45-46ビット目:DPL:00:セグメントの特権レベルをリング0にセットしています
- 47ビット目:Pビット:1:セグメントがメモリ内に有る
DB 11001111b ; Segment Limit Hi、AVL、0、D/B、Gの値を入れます
次は48ビット目から55ビット目までの設定を行なっています
- 48-51ビット目:Segment Limit Hi:1111:セグメントサイズ0xFFFFFの最上位1バイトの値を入れています
- 52ビット目:AVLビット:0:特に使用しないので0を入れています
- 53ビット目:予約領域:0:予約領域です。0にしておきます。
- 54ビット目:D/Bビット:1:32ビット設定にします
- 55ビット目:Gビット:1:セグメントのサイズを4Kバイトとします
DB 0 ; Base Address Hi
ベースアドレス0x00000000の最上位バイト0x00を入れます
ここまでがコードセグメントディスクリプタの設定となります
データディスクリプタ
データディスクリプタはデータを読み書きするセグメントを設定します
こちらも同様に4GBのメモリ空間を1つの大きなセグメントとして扱います
DB 0xFF ; データセグメントのサイズ(Segment Limit)を入れます
DB 0xFF ; サイズは0xFFFFFを入れます
DW 0x0000 ; データセグメントのベースアドレスを入れます。0x00000000を入れます
DB 0x00 ; Base Address Mid
DB 10010010b ; Type、S、DPL、Pの値入れます
DB 11001111b ; Segment Limit Hi、AVL、0、D/B、Gの値を入れます
DB 0 ; Base Address Hi
データセグメントディスクリプタはコードセグメントディスクリプタとほとんど同じ設定となります
違う箇所はコードセグメントとして設定している箇所だけです
DB 10010010b ; Type、S、DPL、Pの値入れます
43ビット目のセグメントの設定で0を入れ、データセグメントとして設定します。このビットのみコードセグメントと違います
TEMPORARYディスクリプタについて
昔に作ったプログラムですので、特に意味も無くテスト用に作っています。特に設ける必要はありません。。。
このディスクリプタを作らない場合はGLDT命令でロードする内容を変更する必要があります
gdt_toc: ; GDTとサイズとアドレスを格納しているアドレスラベル
DW 8*3 ; GDTのサイズ
DD _gdt ; GDTのアドレス
ディスクリプタがNull、コード、データの3つになりますので、上記の用に変更してください
GDTの他にもLDT(Local Descriptor Table)や割り込みを使うときに使用する
IDT(Interrupt Descriptor Table)などがありますが、後ほどご紹介いたします
GDT設定のまとめ
Nullディスクリプタ、コードディスクリプタ、データディスクリプタを用意します
GDTのサイズ、アドレスを設定してLGDT命令でGDTをロードします
ではアセンブラ言語のまとめを見てみましょう
;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
;
; Set up GDT
;
;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
_setup_gdt:
CLI ; 割り込みを禁止します
PUSHA ; AX、CX、DX、BX、SP、BP、SI、DIレジスタをPUSHします
LGDT [gdt_toc] ; GDTをロードします
STI ; 割り込みを有効にします
POPA ; DI、SI、BP、SP、BX、DX、CX、AXにPOPします
RET
;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
;
; Global Descriptor Table
;
;/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
gdt_toc:
DW 4*8
DD _gdt
_gdt: ; このラベルをgdt_tocに登録する
; Null descriptor
DW 0x0000 ; 全部値が0のNullディスクリプタを作ります
DW 0x0000 ; Intel CPUの仕様で決まっています
DW 0x0000
DW 0x0000
; Code descriptor
DB 0xFF ; コードセグメントのサイズ(Segment Limit)を入れます
DB 0xFF ; サイズは0xFFFFFを入れます
DW 0x0000 ; コードセグメントのベースアドレスを入れます。0x00000000を入れます
DB 0x00 ; Base Address Mid
DB 10011010b ; Type、S、DPL、Pの値入れます
DB 11001111b ; Segment Limit Hi、AVL、0、D/B、Gの値を入れます
DB 0 ; Base Address Hi
; Data descriptor
DB 0xFF ; データセグメントのサイズ(Segment Limit)を入れます
DB 0xFF ; サイズは0xFFFFFを入れます
DW 0x0000 ; データセグメントのベースアドレスを入れます。0x00000000を入れます
DB 0x00 ; Base Address Mid
DB 10010010b ; Type、S、DPL、Pの値入れます
DB 11001111b ; Segment Limit Hi、AVL、0、D/B、Gの値を入れます
DB 0 ; Base Address Hi
; TEMPORARY
DW 0x0000
DW 0x0000
DW 0x0000
DW 0x0000
関数_setup_gdtを呼出すことで、GDTの設定を行います
_setup_gdtではLGDT命令でGDTをロードしたときに例外が発生しないようCLIで割り込みを禁止しています
GDTは8192個持つことができますが、8192個もテーブルを持ってしまうと
8バイト×8192個=65536バイト(64Kバイト)もメモリを消費してしまいますので
なるべく少ないテーブルにした方がいいかと思います