X86 分段存储器

X86 分段存储器,x86,kernel,gdt,X86,Kernel,Gdt,我正在尝试构建自己的内核,现在正在设置GDT。我使用一个汇编文件作为加载程序,并调用用C编写的内核,我试图让GDT工作。内核从GRUB引导并通过GRUB刷新GDT设置。当我将GDT条目设置为段(具有适当的限制和偏移量)时,我假设当内核重新启动时,无论是在QEMU中还是在我启动pendrive时,都会出现三重错误 我的问题是: 我可以为x86体系结构实现分段模型吗?是否需要在保护模式下执行?作业完成后,如何退出保护模式 我会在这里发布代码,但大部分代码都来自教程,如果我把汇编代码和C代码混在一起,

我正在尝试构建自己的内核,现在正在设置GDT。我使用一个汇编文件作为加载程序,并调用用C编写的内核,我试图让GDT工作。内核从GRUB引导并通过GRUB刷新GDT设置。当我将GDT条目设置为段(具有适当的限制和偏移量)时,我假设当内核重新启动时,无论是在QEMU中还是在我启动pendrive时,都会出现三重错误

我的问题是:

我可以为x86体系结构实现分段模型吗?是否需要在保护模式下执行?作业完成后,如何退出保护模式

我会在这里发布代码,但大部分代码都来自教程,如果我把汇编代码和C代码混在一起,代码会变得很混乱。最重要的是,如果我在C内核中除了代码段和数据段条目的基之外做了其他任何事情,作为“0”,内核就会重新启动。此外,当我将粒度设置为禁用4KB分页时,也会发生同样的情况。如果需要,请询问更多细节。谢谢:):)

编辑:这是我用来链接asm引导加载程序和C内核文件的linker.ld文件。我已经发布了asm和C文件中与内存分段相关的分段:

链接器:

ENTRY (loader)

SECTIONS
{
    . = 0x00100000;

    .text ALIGN (0x1000) :
    {
        *(.text)
    }

    .rodata ALIGN (0x1000) :
    {
        *(.rodata*)
    }

    .data ALIGN (0x1000) :
    {
        *(.data)
    }

    .bss :
    {
        sbss = .;
        *(COMMON)
        *(.bss)
        ebss = .;
    }
}
C函数设置GDT和实例化函数:

void gdt_set_gate(int num, unsigned long base, unsigned long limit, unsigned char access, unsigned char gran)
{
    /* Setup the descriptor base address */
    gdt[num].base_low = (base & 0xFFFF);
    gdt[num].base_middle = (base >> 16) & 0xFF;
    gdt[num].base_high = (base >> 24) & 0xFF;

    /* Setup the descriptor limits */
    gdt[num].limit_low = (limit & 0xFFFF);
    gdt[num].granularity = ((limit >> 16) & 0x0F);

    /* Finally, set up the granularity and access flags */
    gdt[num].granularity |= (gran & 0xF0);
    gdt[num].access = access;
}

void gdt_install()
{
    /* Setup the GDT pointer and limit */
    gp.limit = (sizeof(struct gdt_entry) * 35) - 1;
    gp.base = &gdt;

    /* Our NULL descriptor */
    gdt_set_gate(0, 0, 0, 0, 0);

   gdt_set_gate(1, 0x00000000, 0xFFFFFFFF, 0x9A, 0xCF); //Setting Code Segment


  gdt_set_gate(2, 0x00000000, 0xFFFFFFFF, 0x92, 0xCF); //Setting Data Segment

  //In the above two, if the second parameter is anything other 
  //than 0 i.e. base is not 0, the kernel doesn't run. 
 //Moreover, setting the last to 0x4F, which is byte accessing rather 
 //than 4KB paging gives the same malfunction too. 

 //gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xCF);
  //gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xCF);     
    //TASK STATE SEGMENT -1 
    //TASK STATE SEGMENT -2 

    //gdt_set_gate(3, 0, 0xFFFFFFFF, 0x89, 0xCF);
   // gdt_set_gate(4, 0, 0xFFFFFFFF, 0x89, 0xCF);   









    /* Flush out the old GDT and install the new changes! */
    gdt_flush();
}
最后,在ASM中编写的GDT flush函数:

gdt_flush:    
    lgdt [gp]        ; Load the GDT with our '_gp' which is a special pointer   
    ;ltr [0x18]
    mov ax, 0x10      ; 0x10 is the offset in the GDT to our data segment
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    jmp 0x08:flush2   ; 0x08 is the offset to our code segment: Far jump!
flush2:
    ret
我知道C函数和ASM函数是正确的,因为它们适用于平面内存模型。是否有任何具体的变化,我需要作出,我想一些建议的链接器文件设置分段或分段分页

我可以为x86体系结构实现分段模型吗

当然可以

是否需要在保护模式下执行

您只有两种选择:带笨拙的真实模式和64K以及其他限制和保护模式

作业完成后,如何退出保护模式

简单地说,关闭页面翻译(如果打开),跳转到16位代码段,加载段寄存器,选择器指向与实模式兼容的描述符,清除CR0.PE。还有更多细节(例如任务切换、中断处理)。您可以在官方文档和一些在线教程中找到它们

如果我在C内核中除了代码段和数据段条目的基数都是“0”之外还做了其他事情,那么内核就会重新启动

您必须了解,x86代码不是位置独立的,如果重新定位到地址X,但加载到地址Y,则无法正常运行。这意味着不仅要调整段描述符的基数,才能将代码/数据移动到某个位置,但是链接器也应该将可执行映像重新定位到另一个位置。一个简单的错误就足以让这不起作用

此外,当我将粒度设置为禁用4KB分页时,也会发生同样的情况

那句话毫无意义。不能通过使用描述符的粒度位来禁用分页

如果需要,请询问更多细节


你的工作就是为我们提供足够的信息来帮助你。正如目前所说的那样,这个问题缺乏细节,无法得到充分回答。您有一些错误代码,但您没有向我们展示,我们如何提供帮助?

在加载GDT后在asm代码中使用此代码

mov eax, cr0
or eax, 1b
mov cr0, eax

你不应该使用分段。您应该使用分页。分段是一个古老的概念,在较新的英特尔处理器上不受支持。只要使用分页的标准。@Linuxios我就不那么肯定了。即使在64位模式下,您仍然使用
GS
组织对线程本地存储(TLS)的访问。假设
gdt_install()
位于偏移量0x123456处,并且在从CS.base=0开始时可运行。在这种情况下,CPU从物理地址0x123456(基+偏移量)获取
gdt_install()
的第一条指令。然后将CS.base更改为0x10000。在这种情况下,CPU仍然需要从物理地址0x123456获取第一条指令(因为代码在内存中,您没有移动它!),但它将从物理地址0x133456获取它。你能看到问题吗?是的,我看到问题了。我需要让加载器将第一次调用gdt_install()后的整个函数或至少部分移动到代码段偏移量。我的问题是,链接器在这一切中扮演了什么角色?@alexey:是的。但是您应该仍然对其他所有内容使用分页。我已经添加了相应的代码。如果您能再看一遍并让我知道,我将不胜感激。谢谢:):)