在上篇文章中,全部代码都用汇编实现,全部代码都挤在一堆,实在有点混乱,我们手中还有C这个武器,它的可读性无疑比汇编高得多。C当然不能完全取代汇编,但相当大的程度上是能使用C的。
下面来分析一下在哪些内容能用C取代汇编的,一开始的段描述符(Descriptor)和门描述符(Gate)当然能用C的结构来描述,就不用写汇编里的宏,这无疑是一个进化;GDT和IDT可以用C来描述,不就是一个Descriptor的数组和一个Gate的数组吗?要填充的GDTR和IDTR的结构更简单,不就是两个6字节的数组吗?下面让我们来一点一点的实现。
Descriptor和Gate的结构的C描述(根据下面两张图的结构得出):
- typedef struct s_descriptor
- {
- u16 limit_low;
- u16 base_low;
- u8 base_mid;
- u8 attr1;
- u8 limit_high_attr2;
- u8 base_high;
- }Descriptor;
-
- typedef struct s_gate
- {
- u16 offset_low;
- u16 selector;
- u8 dcount;
- u8 attr;
- u16 offset_high;
- }Gate;
要填充的GDTR和IDTR的数组和GDT,IDT如下:
- u8 GDT_Ptr[6];
- u8 IDT_Ptr[6];
- Descriptor GDT[128];
- Gate IDT[256];
上面的u8,u16可能你已经猜得出是什么意思了,就是一些typedef,目的是让我们一眼就看出来一个数是几位的:
- typedef unsigned char u8;
- typedef unsigned short u16;
- typedef unsigned int u32;
接下来就是要写填充GDT和IDT的代码了,在汇编中我们可以直接用宏填充,这里我们用C来写,就不能直接填充了,除非在C中也用宏就可以。不过我不太喜欢用宏,就把它抽象成一个函数吧,入口参数有一下几个,要填充的GDT的偏移号;段基址,段界限,段属性。
- void Fill_Desc(u8 desc_no,u32 base,u32 limit,u16 attr)
- {
- Descriptor *p_Desc = (Descriptor *)&GDT[desc_no];
- p_Desc->limit_low = limit & 0xffff;
- p_Desc->base_low = base & 0xffff;
- p_Desc->base_mid = (base >> 16) & 0xff;
- p_Desc->attr1 = attr & 0xff;
- p_Desc->limit_high_attr2 = ((limit >> 16) & 0xf) | (attr >> 8);
- p_Desc->base_high = (base >> 24) & 0xff;
- }
OK,今天的工作到此为止,呵呵,慢慢来嘛,离这学期结束还有一段时候嘛,饭要一口一口吃。到目前为止,我们想验证一下我们的工作成果,那就马上写一个来看看。要说明的一点是,我们先写出合格代码,然后再合理的组织它们。
kernel.asm:
extern GDT_Ptr
extern C_Start
Selector_Loader_Flat_RW equ 8
Selector_Kernel_Flat_RW equ 8
Selector_Kernel_Flat_C equ 16
Selector_Kernel_Video equ 24
[section .text]
global _start
_start:
mov ax,Selector_Loader_Flat_RW
mov ds,ax
mov es,ax
call C_Start
lgdt [GDT_Ptr]
jmp Selector_Flat_C:_test
_test:
mov ax,Selector_Kernel_Video
mov gs,ax
mov ah,0ch
mov al,'V'
mov [gs:(80 * 20) * 2],ax
hlt
start.c:
-
- typedef unsigned char u8;
- typedef unsigned short u16;
- typedef unsigned int u32;
-
-
- #define DA_32 0x4000 /* 32 位段 */
- #define DA_LIMIT_4K 0x8000 /* 段界限粒度为 4K 字节 */
- #define DA_DPL0 0x00 /* DPL = 0 */
- #define DA_DPL1 0x20 /* DPL = 1 */
- #define DA_DPL2 0x40 /* DPL = 2 */
- #define DA_DPL3 0x60 /* DPL = 3 */
-
-
- #define DA_DR 0x90 /* 存在的只读数据段类型值 */
- #define DA_DRW 0x92 /* 存在的可读写数据段属性值 */
- #define DA_DRWA 0x93 /* 存在的已访问可读写数据段类型值 */
- #define DA_C 0x98 /* 存在的只执行代码段属性值 */
- #define DA_CR 0x9A /* 存在的可执行可读代码段属性值 */
- #define DA_CCO 0x9C /* 存在的只执行一致代码段属性值 */
- #define DA_CCOR 0x9E /* 存在的可执行可读一致代码段属性值 */
-
-
- #define DA_LDT 0x82 /* 局部描述符表段类型值 */
- #define DA_TaskGate 0x85 /* 任务门类型值 */
- #define DA_386TSS 0x89 /* 可用 386 任务状态段类型值 */
- #define DA_386CGate 0x8C /* 386 调用门类型值 */
- #define DA_386IGate 0x8E /* 386 中断门类型值 */
- #define DA_386TGate 0x8F /* 386 陷阱门类型值 */
-
- typedef struct s_descriptor
- {
- u16 limit_low;
- u16 base_low;
- u8 base_mid;
- u8 attr1;
- u8 limit_high_attr2;
- u8 base_high;
- }Descriptor;
-
- Descriptor GDT[128];
- u8 GDT_Ptr[6];
-
- void Fill_Desc(u8 desc_no,u32 base,u32 limit,u16 attr)
- {
- Descriptor *p_Desc = (Descriptor *)&GDT[desc_no];
- p_Desc->limit_low = limit & 0xffff;
- p_Desc->base_low = base & 0xffff;
- p_Desc->base_mid = (base >> 16) & 0xff;
- p_Desc->attr1 = attr & 0xff;
- p_Desc->limit_high_attr2 = ((limit >> 16) & 0xf) | (attr >> 8);
- p_Desc->base_high = (base >> 24) & 0xff;
- }
-
- void Init_GDT()
- {
- Fill_Desc(0,0,0,0);
- Fill_Desc(1,0,0xfffff,DA_DRW | DA_32 | DA_LIMIT_4K);
- Fill_Desc(2,0,0xfffff,DA_C | DA_32 | DA_LIMIT_4K);
- Fill_Desc(3,0xb8000,0xffff,DA_DRW);
- }
-
- void C_Start()
- {
- Init_GDT();
- *(u16*)(&GDT_Ptr[0]) = 4 * 8;
- *(u32*)(&GDT_Ptr[2]) = (u32)&GDT;
- }
编译链接方法:nasm -f elf -o kernel.o kernel.asm
gcc -c -o start.o start.c
ld -s -Ttext 0x30400 -o kernel.bin kernel.o start.o
我们仍然是输出一个字母,这次选择的是V,呵呵,表示胜利。
最后照例是附图一张,看看通过C来设置GDT是否正确。