只要PC机上安装了 Ubuntu Linux 操作系统,GRUB 就已经存在了。
首先,什么叫”GRUB引导程序“,为什么装了Ubuntu就有?
GRUB (Grand Unified Bootloader) 是一个多启动引导程序,它负责在计算机启动时加载并将控制权传递给操作系统内核软件。
GRUB 是 Linux 系统中常用的默认引导程序,包括 Ubuntu可以通过配置 GRUB 来更改默认操作系统、设置操作系统超时时间以及更改 GRUB 背景图像等。
多引导协议头是什么
多引导协议头(Multiboot Header)是一个用于启动操作系统的标准,最初由GNU项目开发。多引导协议定义了一个操作系统内核映像应该包含的一些信息和结构,以便与兼容的引导加载程序(例如GRUB和GRUB2)一起使用。通过遵循这个标准,操作系统内核可以被多种不同的引导加载程序所加载和启动,从而提高了内核的兼容性和可移植性。
多引导协议头主要包括以下信息:
魔数(Magic Number):一个32位的值,用于标识内核映像支持多引导协议。对于Multiboot 1规范(GRUB),魔数是0x1BADB002;对于Multiboot 2规范(GRUB2),魔数是0xE85250D6。
MBT_HDR_MAGIC EQU 0x1BADB002;定义一个常量,代表第一版Multiboot标头魔数。
MBT_HDR2_MAGIC EQU 0xe85250d6;定义一个常量,代表第二版Multiboot标头魔数
标志(Flags):一个32位的值,用于表示内核映像支持的多引导协议的特性和要求。例如,标志可以指定是否需要引导加载程序提供内存映射信息、视频模式信息等。
MBT_HDR_FLAGS EQU 0x00010003;定义一个常量,代表Multiboot标头标志。
检查和(Checksum):一个32位的值,用于验证多引导协议头的完整性。通常,魔数、标志和检查和的和应该为零。
;表示校验和(Checksum),确保头魔数、头标志和校验和相加的结果为 0。这是一个简单的错误检查机制,以确保头部没有被破坏。
dd -(MBT_HDR_MAGIC+MBT_HDR_FLAGS)
dd
是汇编语言中的一个指令,表示 "define doubleword"(定义双字)。双字(doubleword)是一个 32 位(4 字节)的数据类型。dd
指令用于在内存中分配并初始化一个双字变量。在这段代码中,dd
用于初始化多引导协议头的字段。4. 头地址(Header Address):多引导协议头在内核映像中的地址。
;表示头地址(Header Address),即多引导协议头的起始位置。
dd mbt_hdr
加载地址(Load Address):内核映像在内存中的加载地址。
;表示加载地址(Load Address),即操作系统代码的加载地址。
dd _start
其他信息:根据多引导协议的版本和所需特性,可能需要包含其他信息,如内核映像的入口点、视频模式信息等。在操作系统内核开发过程中,多引导协议头通常位于内核映像的开头部分,并通过汇编语言编写。将多引导协议头添加到内核映像后,可以使用兼容的引导加载程序(如GRUB或GRUB2)加载和启动内核。
GRUB汇编代码解析
;彭东 @ 2021.01.09
;定义了两个多引导协议头
;一个用于GRUB(GRand Unified Bootloader)
;另一个用于GRUB2
;这样做的目的是让代码与GRUB Legacy和GRUB2同时兼容。
MBT_HDR_FLAGS EQU 0x00010003
MBT_HDR_MAGIC EQU 0x1BADB002 ;多引导协议头魔数
MBT_HDR2_MAGIC EQU 0xe85250d6 ;第二版多引导协议头魔数
;导出_start符号
;实际上,_start 这个标记本身并没有任何特殊含义,它只是一个人们约定好的,长久以来一直被用于指代程序入口的名字。通常来说,_start 被更多地用在类 Unix 系统中,它是链接器在生成目标可执行文件时,会默认使用的一个符号名称。链接器在链接过程中,会在全局符号表中找到该符号,并将其虚拟地址直接存放到所生成的可执行文件里。具体来说,它会将这个值拷贝至 ELF 头的 e_entry 字段中。
global _start
;导入外部的main函数符号,这表示代码将调用一个用C语言编写的main函数。
extern main
[section .start.text] ;定义.start.text代码节
[bits 32] ;汇编成32位代码
;在代码片段的开始部分,定义了一个名为"_start"的标签,这是程序的入口点。在这个标签下,有一个跳转到"_entry"的指令。
_start:
jmp _entry
;将内存对齐到8字节边界.这有助于提高内存访问性能,并确保遵循某些平台的内存对齐要求。
ALIGN 8
;GRUB Legacy所需的Multiboot标头从此开始,到dd _entry结束
mbt_hdr:
dd MBT_HDR_MAGIC;表示多引导协议头的魔数,用于识别多引导协议头。
dd MBT_HDR_FLAGS;表示多引导协议头的标志,用于指定引导加载器应支持哪些特性。
dd -(MBT_HDR_MAGIC+MBT_HDR_FLAGS);表示校验和(Checksum),确保头魔数、头标志和校验和相加的结果为 0。这是一个简单的错误检查机制,以确保头部没有被破坏。
dd mbt_hdr;表示头地址(Header Address),即多引导协议头的起始位置
dd _start;表示加载地址(Load Address),即操作系统代码的加载地址。
dd 0;表示初始化一个双字变量,其值为 0。在这段代码中,它用于初始化多引导协议头中预留的未使用字段,填充 0 以确保这些字段不包含任何意外的数据。
dd 0;有两个 dd 0 用于初始化多引导协议头中的两个预留字段。这些字段在当前的多引导协议实现中没有特定的用途,但保留它们以供未来的扩展和兼容性。
dd _entry;表示初始化一个双字变量,其值为 _entry 标签的地址。在这段代码中,这个变量用于指示操作系统入口点的地址。这使得引导加载器(例如 GRUB)能够知道从何处开始执行操作系统代码。
;以上是GRUB Legacy所需要的头
;将内存对齐到8字节边界
ALIGN 8
;定义一个标签,表示 GRUB2 多引导协议头的起始位置。
mbt2_hdr:
DD MBT_HDR2_MAGIC;定义一个双字变量,其值为 GRUB2 多引导协议头的魔数。
DD 0;定义一个双字变量,其值为 0。这是一个预留字段。
DD mbt2_hdr_end - mbt2_hdr;定义一个双字变量,表示多引导协议头的长度(从 mbt2_hdr 到 mbt2_hdr_end)。
DD -(MBT_HDR2_MAGIC + 0 + (mbt2_hdr_end - mbt2_hdr));定义一个双字变量,表示多引导协议头的校验和。这确保头魔数、预留字段、头长度和校验和相加的结果为 0。
DW 2, 0;定义一个字变量,表示头中的一个标签类型(类型 2)。DW 表示 "define word"(定义字),字是一个 16 位(2 字节)的数据类型。接下来的 0 是此标签的标志字段。
DD 24;定义一个双字变量,表示此类型 2 标签的长度(24 字节)。
DD mbt2_hdr;定义一个双字变量,表示多引导协议头的起始地址。
DD _start;定义一个双字变量,表示操作系统代码的加载地址。
DD 0
DD 0
DW 3, 0;定义一个字变量,表示头中的另一个标签类型(类型 3)。接下来的 0 是此标签的标志字段。
DD 12;定义一个双字变量,表示此类型 3 标签的长度(12 字节)。
DD _entry;定义一个双字变量,表示操作系统入口点的地址。
DD 0;定义一个双字变量,其值为 0。这是一个预留字段。
DW 0, 0;定义一个字变量,表示头中的终止标签类型(类型 0)。接下来的 0 是此标签的标志字段。
DD 8;定义一个双字变量,表示终止标签的长度(8 字节)。
mbt2_hdr_end:;定义一个标签,表示 GRUB2 多引导协议头结束
;以上是GRUB2所需要的头
;包含两个头是为了同时兼容GRUB、GRUB2
ALIGN 8
;在"_entry"标签下,有一系列用于初始化CPU寄存器和设置中断、GDT(全局描述符表)的指令。这是为了准备好环境以便调用C语言编写的main函数。
_entry:;定义一个代码入口点的标签,名为 _entry。
cli;禁用中断。这个指令会关闭 CPU 的中断响应,防止在引导过程中发生意外的中断。
;关不可屏蔽中断
;NMI(Non-Maskable Interrupt,不可屏蔽中断)是一种特殊类型的硬件中断,用于处理一些紧急情况或关键事件。NMI 与其他中断(如可屏蔽中断)的主要区别在于,NMI 在默认情况下不能被屏蔽或禁用。这意味着,当 NMI 信号到达 CPU 时,CPU 必须立即响应并处理相关事件。
;AL 寄存器(Accumulator Low)是 x86 架构中 8 位通用寄存器 AX(Accumulator Register)的低 8 位部分。AX 寄存器本身是 16 位宽,由 AH(Accumulator High,高 8 位)和 AL(低 8 位)两部分组成。
in al, 0x70;将端口 0x70 的数据读入 AL 寄存器。这个指令用于读取 CMOS RAM 地址寄存器。
or al, 0x80;将 AL 寄存器中的值与 0x80(1000 0000)进行按位或操作。这会将最高位设置为 1,表示禁用 NMI(不可屏蔽中断)。
out 0x70,al;将 AL 寄存器的值输出到端口 0x70。这个指令将修改后的值写回 CMOS RAM 地址寄存器,从而禁用 NMI。
;重新加载GDT
lgdt [GDT_PTR];加载全局描述符表(GDT)的地址。GDT_PTR 是一个指向 GDT 的指针,在稍后的代码中定义(最底部)
;跳转到指定的段选择子(选择子值为 0x8)并执行 _32bits_mode 标签处的代码。这里 0x8 对应的是全局描述符表中的代码段描述符。
jmp dword 0x8 :_32bits_mode
;定义一个名为 _32bits_mode 的标签,表示 32 位保护模式下的代码入口。
_32bits_mode:
;下面初始化C语言可能会用到的寄存器
;这段代码的目的是初始化CPU寄存器,为接下来调用C语言编写的main函数做准备。在调用其他函数之前,通常需要将寄存器设置为合适的初始值以避免出现问题。
mov ax, 0x10;将0x10(16进制)赋值给寄存器AX。0x10 是 GDT 中的数据段描述符的选择子,将用于设置数据段寄存器。
mov ds, ax;将AX的值(0x10)赋值给数据段寄存器DS。
mov ss, ax;将AX的值(0x10)赋值给栈段寄存器SS。
mov es, ax;将AX的值(0x10)赋值给附加段寄存器ES。
mov fs, ax;将AX的值(0x10)赋值给附加段寄存器FS。
mov gs, ax;将AX的值(0x10)赋值给附加段寄存器GS。
xor eax,eax;将EAX寄存器与自身异或,结果是0。这是一种高效地将寄存器清零的方法。
xor ebx,ebx;将EBX寄存器与自身异或,结果是0。同样地,将寄存器清零。
xor ecx,ecx;将ECX寄存器与自身异或,结果是0。将寄存器清零。
xor edx,edx;将EDX寄存器与自身异或,结果是0。将寄存器清零。
xor edi,edi;将EDI寄存器与自身异或,结果是0。将寄存器清零。
xor esi,esi;将ESI寄存器与自身异或,结果是0。将寄存器清零。
xor ebp,ebp;将EBP寄存器与自身异或,结果是0。将寄存器清零。
xor esp,esp;将ESP寄存器与自身异或,结果是0。将寄存器清零。
;初始化栈,给 C 语言运行时环境提供一个栈空间,因为 C 语言需要栈来进行函数调用、局部变量存储等操作。
mov esp,0x9000
;调用C语言函数main
call main
;让CPU停止执行指令
halt_step:;halt_step: 是一个标签,用于标识一个循环,该循环使 CPU 停止执行指令。
halt ;汇编指令,用于暂停 CPU,直到收到中断信号。
jmp halt_step; 无条件跳转指令,将执行流跳回 halt_step 标签。这样就形成了一个无限循环,使得 CPU 在执行完 main 函数后,不再执行任何其他指令。
;定义 GDT(全局描述符表):GDT 是一个包含内存段描述符的表,用于在保护模式下描述内存段的基址、大小、访问权限等属性。这里定义了以下描述符:
GDT_START:
knull_dsc: dq 0;knull_dsc:空描述符,全零。
kcode_dsc: dq 0x00cf9e000000ffff;kcode_dsc:内核代码段描述符。
kdata_dsc: dq 0x00cf92000000ffff;kdata_dsc:内核数据段描述符。
k16cd_dsc: dq 0x00009e000000ffff;k16cd_dsc:16 位代码段描述符。
k16da_dsc: dq 0x000092000000ffff;k16da_dsc:16 位数据段描述符。
GDT_END:
;定义 GDT_PTR:GDT_PTR 是一个数据结构,用于存储 GDT 的长度和基址。这个数据结构在前面的 lgdt 指令中使用,以加载 GDT 到 GDTR 寄存器。
GDT_PTR:
GDTLEN dw GDT_END-GDT_START-1;GDTLEN:GDT 的长度,等于 GDT_END 和 GDT_START 之间的字节数减 1。
GDTBASE dd GDT_START;GDTBASE:GDT 的基址,等于 GDT_START 的地址。
为什么需要GRUB和GRUB2呢?可以只保留一个么?
GRUB(GRand Unified Bootloader,大一统引导程序)和GRUB2是两个不同版本的引导加载程序。虽然它们的核心功能类似,即加载并启动操作系统内核,但它们之间存在一些关键区别。以下是它们之间的一些主要差异:
配置文件:GRUB和GRUB2的配置文件格式和语法有所不同。GRUB使用较简单的menu.lst或grub.conf文件,而GRUB2使用更复杂且模块化的grub.cfg文件。 功能和模块化:GRUB2具有更强大的功能,如支持更多的文件系统、磁盘驱动器类型和分区表。GRUB2采用模块化设计,可以根据需要加载不同的功能模块,从而减小引导加载程序的大小并提高其可扩展性。 多引导协议:GRUB遵循Multiboot 1规范,而GRUB2遵循Multiboot 2规范。虽然两者之间的差异不大,但Multiboot 2规范提供了一些额外的特性和改进,如UEFI支持、更高的内存分辨率等。
从理论上讲,您可以选择仅使用GRUB或GRUB2来引导您的操作系统。但是,由于操作系统内核可以同时包含Multiboot 1和Multiboot 2的头信息,因此保留对两者的兼容性可以提高操作系统的通用性。这样,如果在某些环境中一个引导加载程序不可用或不支持特定功能,另一个引导加载程序可以作为备选方案。这在操作系统发行版和实际应用场景中尤为重要。
总之,尽管可以只保留一个引导加载程序,但同时支持GRUB和GRUB2可以提高操作系统的兼容性和灵活性。在许多情况下,为了向后兼容性和对现有系统的支持,同时支持两者是有意义的。
多引导标签(Multiboot Tags)在 GRUB2 中的类型
类型 0:结束标签(End Tag) 结束标签用于表示多引导标签列表的终止位置,以便 GRUB2 知道在哪里停止解析标签列表。类型 0 标签通常紧跟在其他类型的标签之后。 类型 1:命令行标签(Boot Command Line) 命令行标签包含一个指向以空字符结尾的字符串的指针,该字符串表示操作系统的启动命令行参数。 类型 2:引导加载器名称标签(Boot Loader Name) 引导加载器名称标签包含一个指向以空字符结尾的字符串的指针,该字符串表示引导加载器的名称。 类型 3:模块标签(Module) 模块标签表示与操作系统一起加载的模块。每个模块标签包含一个指向模块文件的内存区域的指针、模块文件的大小和一个以空字符结尾的字符串,表示模块的命令行参数。 类型 4:基本内存信息标签(Basic Memory Information) 基本内存信息标签提供有关计算机上可用的低内存(位于 1MB 以下)和高内存(位于 1MB 以上)的信息。 类型 5:BIOS Boot Device BIOS Boot Device 标签包含一个描述启动设备的结构,包括驱动器编号、分区编号等信息。 类型 6:内存映射标签(Memory Map) 内存映射标签提供了一个描述系统物理内存布局的内存映射,包括可用内存和保留内存区域。 类型 7:VBE 标签(VESA BIOS Extensions) VBE 标签提供了有关 VESA BIOS 扩展的信息,包括 VBE 模式、控制器信息等。 类型 8:帧缓冲标签(Framebuffer Information) 帧缓冲标签提供了有关图形帧缓冲区的信息,包括帧缓冲区的地址、大小、颜色深度等。 类型 9:ELF 标签(ELF Sections) ELF 标签提供了有关 ELF 可执行文件的段信息,包括段的类型、地址、大小等。 类型 10:APM 表(Advanced Power Management Table) APM 表标签提供了有关 APM(高级电源管理)的信息,包括版本号、状态等。 类型 11:EFI32 表(EFI System Table 32-bit) EFI32 表标签提供了一个指向 32 位 EFI 系统表的指针。 类型 12:EFI64 表(EFI System 统表 64-bit) EFI64 表标签提供了一个指向 64 位 EFI 系统表的指针。 类型 13:SMBIOS 表(System Management BIOS Table) SMBIOS 表标签提供了一个指向 SMBIOS(系统管理 BIOS)表的指针,以及 SMBIOS 的版本信息。 类型 14:ACPI 旧版表(Old ACPI Table) ACPI 旧版表标签提供了一个指向旧版 ACPI(高级配置与电源接口)表的指针。 类型 15:ACPI 新版表(New ACPI Table) ACPI 新版表标签提供了一个指向新版 ACPI 表的指针。 类型 16:网络表(Network Table) 网络表标签提供了一个指向网络配置表的指针,该表包含了启动时网络配置的信息。 类型 17:EFI Memory Map(EFI 内存映射) EFI Memory Map 标签提供了一个描述 EFI 系统物理内存布局的内存映射,包括可用内存和保留内存区域。 类型 18:EFI Boot Services Not Terminated(EFI 引导服务未终止) 该标签表示 EFI 引导服务在操作系统接管之前未终止,操作系统需要自行终止这些服务。 类型 19:EFI 32位运行时服务(EFI Runtime Services 32-bit) 该标签提供了一个指向 32 位 EFI 运行时服务的指针。 类型 20:EFI 64位运行时服务(EFI Runtime Services 64-bit) 该标签提供了一个指向 64 位 EFI 运行时服务的指针。 类型 21:SMBIOS 3.0 表(SMBIOS 3.0 Table) SMBIOS 3.0 表标签提供了一个指向 SMBIOS 3.0(系统管理 BIOS)表的指针,以及 SMBIOS 3.0 的版本信息。
这些类型用于描述操作系统启动过程中所需的各种信息和参数。具体哪些类型在特定的引导过程中使用,取决于操作系统和引导加载器的具体实现。
NMI
NMI(Non-Maskable Interrupt,不可屏蔽中断)是一种特殊类型的硬件中断,用于处理一些紧急情况或关键事件。NMI 与其他中断(如可屏蔽中断)的主要区别在于,NMI 在默认情况下不能被屏蔽或禁用。这意味着,当 NMI 信号到达 CPU 时,CPU 必须立即响应并处理相关事件。
NMI 的典型应用包括:
系统硬件故障或异常事件:NMI 可以用于检测到硬件故障,如内存奇偶校验错误、电源问题或其他关键硬件组件的故障。 软件调试:某些情况下,NMI 可以用于软件调试。例如,当程序执行过程中出现死锁时,可以使用 NMI 生成一个断点,从而进入调试器来分析问题。
在操作系统引导过程中,通常需要禁用 NMI,以确保在关键代码执行过程中不会受到意外中断的干扰。当系统稳定运行后,可以根据需要启用 NMI,以便在出现紧急事件时得到响应。
AL 寄存器
AL 寄存器(Accumulator Low)是 x86 架构中 8 位通用寄存器 AX(Accumulator Register)的低 8 位部分。AX 寄存器本身是 16 位宽,由 AH(Accumulator High,高 8 位)和 AL(低 8 位)两部分组成。
在 x86 汇编语言中,AL 寄存器通常用于存储与算术、逻辑操作和数据传输相关的临时数据。由于 AL 是一个 8 位寄存器,它可以处理 8 位(1 字节)的数据。
在前面的代码示例中,AL 寄存器用于存储从 CMOS RAM 地址寄存器(端口 0x70)读取的值,然后对该值进行修改以禁用不可屏蔽中断(NMI),最后将修改后的值写回到 CMOS RAM 地址寄存器。这里的 AL 寄存器充当了一个临时存储器,用于在操作过程中存储数据。
selector
选择子(Selector)是x86和x86-64体系结构下,用于访问全局描述符表(Global Descriptor Table, GDT)或局部描述符表(Local Descriptor Table, LDT)中的描述符的一个索引值。GDT和LDT包含了用于管理内存段的描述符,它们定义了内存段的基址、大小和访问权限等属性。
在实模式下,CPU使用段基址和偏移地址来计算线性地址。而在保护模式下,CPU使用选择子来引用描述符表中的描述符,进而获取段基址、段限制和访问权限等信息,然后再将段基址与偏移地址相加以获得线性地址。
选择子是一个16位的值,它包含以下几部分:
索引(Index):选择子的高13位用于表示描述符表(GDT或LDT)中的描述符索引。索引值乘以8(描述符的字节大小)得到描述符在表中的偏移量。 表指示符(TI, Table Indicator):选择子的第14位(从0开始计数)表示描述符位于GDT(0)还是LDT(1)。 请求特权级(RPL, Requested Privilege Level):选择子的最低两位(15-16位)表示访问请求的特权级别(0-3),0表示最高权限,3表示最低权限。
例如,在前面的代码片段中,mov ax, 0x10
将选择子设置为0x10(16进制),对应的二进制表示是00010000
。这意味着:
索引: 0001 0000 0000
,对应的十进制值为2,表示该选择子对应GDT中的第2个描述符。表指示符: 0
,表示该选择子对应GDT。请求特权级: 00
,表示请求的特权级别为0(最高权限)。
选择子通常用于设置段寄存器,例如DS(数据段)、SS(栈段)、ES(附加段)、FS和GS。设置段寄存器后,CPU就能正确地访问内存中相应的段。
DS, SS, ES, FS, GS 这些寄存器有什么区别?
在x86和x86-64体系结构中,DS、SS、ES、FS和GS都是段寄存器。它们的主要作用是存储选择子(在保护模式下)或段基址(在实模式下),用于内存寻址。这些寄存器的名称表示了它们在不同情境下的典型用途,但它们可以互换使用。以下是这些段寄存器的一般用途:
DS(Data Segment):数据段寄存器,通常用于存储程序数据所在内存段的选择子(保护模式)或段基址(实模式)。大多数数据访问指令隐式地使用DS寄存器,例如MOV、ADD等。
SS(Stack Segment):栈段寄存器,用于存储程序栈所在内存段的选择子(保护模式)或段基址(实模式)。当使用栈操作指令(如PUSH、POP、CALL和RET等)时,隐式地使用SS寄存器。
ES(Extra Segment):附加段寄存器,用于存储额外的内存段选择子或段基址。在某些内存操作指令中,需要使用两个段寄存器,例如字符串操作指令(如MOVS、CMPS等),这时会显式地使用ES寄存器。
FS和GS:附加段寄存器,它们的用途与ES类似,但在不同的操作系统和应用程序中有特定的用途。例如,在Windows和Linux操作系统中,FS寄存器通常用于访问线程局部存储(Thread-Local Storage, TLS),而GS寄存器在x86-64体系结构的Linux和macOS中用于访问TLS。
虽然这些寄存器具有特定的用途,但在实践中,它们可以互换使用,只要正确地设置选择子或段基址。然而,为了遵循惯例和提高代码的可读性,建议使用这些寄存器的典型用途。
通用寄存器:
EAX(Extended Accumulator):累加器,用于存储一般计算结果,如加法、乘法等。 EBX(Extended Base):基址寄存器,用于存储指针和地址。 ECX(Extended Counter):计数器,用于存储循环计数值和字符串操作。 EDX(Extended Data):数据寄存器,通常与 EAX 配合使用,如双字乘法和双字除法。 EDI(Extended Destination Index):目的地址索引寄存器,用于存储字符串操作的目的地址。 ESI(Extended Source Index):源地址索引寄存器,用于存储字符串操作的源地址。 EBP(Extended Base Pointer):基址指针,用于指向栈帧的基地址,常用于访问局部变量和函数参数。 ESP(Extended Stack Pointer):堆栈指针,用于指向栈顶元素。
这些寄存器在程序执行过程中发挥着重要作用,可以用于存储临时数据、计算结果、地址和指针等。在 x86-64 架构中,这些寄存器都有 64 位版本,如 RAX、RBX、RCX 等。
lgdt是什么?GDT_PTR在哪里定义?
lgdt
(Load Global Descriptor Table)是一个 x86 汇编指令,用于加载全局描述符表(GDT)的基址和大小信息到 GDTR 寄存器。GDT 是一个包含内存段描述符的表,这些描述符用于定义内存段的基址、大小、访问权限和其他属性。GDT 对于实现内存保护和在实模式与保护模式之间切换非常重要。
在给定的代码中,lgdt [GDT_PTR]
指令用于加载 GDT_PTR 指向的数据结构到 GDTR 寄存器。GDT_PTR 在稍后的代码中定义:
GDT_PTR:
GDTLEN dw GDT_END-GDT_START-1
GDTBASE dd GDT_START
这里,GDT_PTR 是一个包含两个字段的数据结构:
GDTLEN(一个字,16 位):GDT 的长度(GDT_END 和 GDT_START 之间的字节数减 1)。 GDTBASE(一个双字,32 位):GDT 的基址(GDT_START 的地址)。
加载 GDT_PTR 后,GDT 就会被正确设置,操作系统可以使用 GDT 中定义的内存段进行内存操作。




