在 assemble 时定义 IDT 或从 bootloader/kernel 以编程方式在内存中构建它更实用吗?
Is it more practical to define an IDT at assemble time, or build it in memory programmatically from a bootloader/kernel?
在我看来,为自己编写一个完整的 IDT 需要做很多工作。编写所有处理程序等。即使使用宏指令和 "times" 指令来帮助您。如果 IDT
由 256 个 qwords(或多或少)组成,包含要调用的 ir 处理程序、标志、段选择器等信息。让自己进入 pmode,为自己选择一个内存位置并以编程方式在那里创建所需的一切不是更容易吗?如果你从地址 0x7bfff 开始,构建所有 256 个条目直到 0x7ffff,确保给它们一个通用处理程序的地址,以便从标志、选择器等调用更具体的处理程序。然后你知道你的基础和限制。然后只要填入idt指针,加载它,猎物它就可以了。
实用性始终是一种主观的看法。因此,您的问题无法完全回答。尽管如此,我们还是可以看看一些支持和反对在 运行 时间构建中断描述符 table (IDT) 的论据:
基本上,您的问题归结为是否在 运行 或编译时生成常量数据(IDT)。假设不管什么时候都会产生同样数量的数据,我们有一些常用的参数:
- 程序大小:如果包含完整生成的数据,那么程序(在本例中为内核)的大小将会增加,从而可能导致初始加载时间更长。当然,只有当数据可以通过与数据大小相比可以忽略不计的代码生成时才会出现这种情况。
- 执行时间:运行时间生成数据涉及"doing stuff",增加了执行时间。根据生成数据所需的时间以及生成数据的频率,这要么是一个严重的问题,要么可以忽略不计。
在内核的上下文中,我可以想到以下需要在两个选项之间进行思考的情况:
- 用于放置内核(以及其他代码)的有限只读存储器 (ROM)。在这种情况下,在 运行 时间生成 IDT 有利于减少静态内存占用。除非在可用内存(非常)有限的嵌入式设置中,否则不会发生这种情况。
- 关键启动时间。如果在某些硬实时设置中,您的系统关闭了(由于更新、故障……),那么您希望它尽快重新启动。在 运行 时间生成 IDT 可能会稍微增加启动时间。另一方面,加载静态数据也需要时间。恕我直言,您不太可能需要考虑这一点。
对于所有其他情况,此类 "measurable" 指标不会有太大差异。考虑到在调试和 运行 时间生成数据方面存在一些权衡;使用静态 IDT 时,您甚至可以在 运行 内核之前对其进行验证。
这就是实用性的另一个方面:
[..] Writing all the handlers, ect. Even with things like macro's and "times" directives to help you. [..]
当然,这在很大程度上取决于您实际编写静态 IDT 的方式。基本上,没有人会阻止您编写(用户)程序来发出正确的 IDT 以链接到您的内核中,使用与您将放入内核中的代码相同的代码(甚至比这更简单的代码)。因此,必须 "write all the handlers" 是您工具的缺陷,因此与是否在 运行 时间生成它们没有直接关系。
在 hobbyist kernel I wrote long ago 中,我创建了处理程序(代码)"by hand"(我现在会做不同的事情 ^^)和 运行 期间的实际 IDT(数据):
// src/idt/idt.c
void IDT_SetGate (UINT8 num, UINT base, UINT16 sel, UINT8 flags)
{
UINT *tmp;
tmp = IDT;
tmp += num * 2;
*tmp = (base & 0xFFFF);
*tmp |= (sel & 0xFFFF) << 16;
tmp++;
*tmp = (flags & 0xFF) << 8;
*tmp |= (base & 0xFFFF0000);
}
// src/int/isr.c
void ISR_Setup (void)
{
IDT_SetGate(0, (unsigned)_isr0, 0x08, 0x8E);
IDT_SetGate(1, (unsigned)_isr1, 0x08, 0x8E);
// ... a lot more, an array would've been the better choice :D
}
然后我有最少的处理程序 _isr0
、_isr1
、...在保存中断号和处理错误代码后调用了一个通用处理程序 .反对的主要论点是:
[..] making sure to give them the address of a common handler to call more specific handlers from [..]
至少在 x86 上,您需要针对不同的中断使用不同的处理程序,否则您将无法判断触发了哪个中断。要在 运行 时间真正生成处理程序,您需要某种专门的汇编器,它可以帮助您在 运行 时间生成代码。我不会再叫那个 "practical" 了。
在我看来,为自己编写一个完整的 IDT 需要做很多工作。编写所有处理程序等。即使使用宏指令和 "times" 指令来帮助您。如果 IDT 由 256 个 qwords(或多或少)组成,包含要调用的 ir 处理程序、标志、段选择器等信息。让自己进入 pmode,为自己选择一个内存位置并以编程方式在那里创建所需的一切不是更容易吗?如果你从地址 0x7bfff 开始,构建所有 256 个条目直到 0x7ffff,确保给它们一个通用处理程序的地址,以便从标志、选择器等调用更具体的处理程序。然后你知道你的基础和限制。然后只要填入idt指针,加载它,猎物它就可以了。
实用性始终是一种主观的看法。因此,您的问题无法完全回答。尽管如此,我们还是可以看看一些支持和反对在 运行 时间构建中断描述符 table (IDT) 的论据:
基本上,您的问题归结为是否在 运行 或编译时生成常量数据(IDT)。假设不管什么时候都会产生同样数量的数据,我们有一些常用的参数:
- 程序大小:如果包含完整生成的数据,那么程序(在本例中为内核)的大小将会增加,从而可能导致初始加载时间更长。当然,只有当数据可以通过与数据大小相比可以忽略不计的代码生成时才会出现这种情况。
- 执行时间:运行时间生成数据涉及"doing stuff",增加了执行时间。根据生成数据所需的时间以及生成数据的频率,这要么是一个严重的问题,要么可以忽略不计。
在内核的上下文中,我可以想到以下需要在两个选项之间进行思考的情况:
- 用于放置内核(以及其他代码)的有限只读存储器 (ROM)。在这种情况下,在 运行 时间生成 IDT 有利于减少静态内存占用。除非在可用内存(非常)有限的嵌入式设置中,否则不会发生这种情况。
- 关键启动时间。如果在某些硬实时设置中,您的系统关闭了(由于更新、故障……),那么您希望它尽快重新启动。在 运行 时间生成 IDT 可能会稍微增加启动时间。另一方面,加载静态数据也需要时间。恕我直言,您不太可能需要考虑这一点。
对于所有其他情况,此类 "measurable" 指标不会有太大差异。考虑到在调试和 运行 时间生成数据方面存在一些权衡;使用静态 IDT 时,您甚至可以在 运行 内核之前对其进行验证。
这就是实用性的另一个方面:
[..] Writing all the handlers, ect. Even with things like macro's and "times" directives to help you. [..]
当然,这在很大程度上取决于您实际编写静态 IDT 的方式。基本上,没有人会阻止您编写(用户)程序来发出正确的 IDT 以链接到您的内核中,使用与您将放入内核中的代码相同的代码(甚至比这更简单的代码)。因此,必须 "write all the handlers" 是您工具的缺陷,因此与是否在 运行 时间生成它们没有直接关系。
在 hobbyist kernel I wrote long ago 中,我创建了处理程序(代码)"by hand"(我现在会做不同的事情 ^^)和 运行 期间的实际 IDT(数据):
// src/idt/idt.c
void IDT_SetGate (UINT8 num, UINT base, UINT16 sel, UINT8 flags)
{
UINT *tmp;
tmp = IDT;
tmp += num * 2;
*tmp = (base & 0xFFFF);
*tmp |= (sel & 0xFFFF) << 16;
tmp++;
*tmp = (flags & 0xFF) << 8;
*tmp |= (base & 0xFFFF0000);
}
// src/int/isr.c
void ISR_Setup (void)
{
IDT_SetGate(0, (unsigned)_isr0, 0x08, 0x8E);
IDT_SetGate(1, (unsigned)_isr1, 0x08, 0x8E);
// ... a lot more, an array would've been the better choice :D
}
然后我有最少的处理程序 _isr0
、_isr1
、...在保存中断号和处理错误代码后调用了一个通用处理程序 .反对的主要论点是:
[..] making sure to give them the address of a common handler to call more specific handlers from [..]
至少在 x86 上,您需要针对不同的中断使用不同的处理程序,否则您将无法判断触发了哪个中断。要在 运行 时间真正生成处理程序,您需要某种专门的汇编器,它可以帮助您在 运行 时间生成代码。我不会再叫那个 "practical" 了。