X86 为小型内核设计GDT

X86 为小型内核设计GDT,x86,kernel,memory-segmentation,X86,Kernel,Memory Segmentation,我目前正在编写一些小的内核代码。下面是我从某个内核项目复制的内容。它包含用于将内核加载到内存位置0x1000并跳到位置0x1000的代码: ; ; The Bootsector Code (First 512 bytes of the floppy) ; ; ; Define Code Segment and Data Segment Rights details for inputting to GDTFill function ; %define CS_ACCES 10011011b ;

我目前正在编写一些小的内核代码。下面是我从某个内核项目复制的内容。它包含用于将内核加载到内存位置0x1000并跳到位置0x1000的代码:

;
; The Bootsector Code (First 512 bytes of the floppy)
;

;
; Define Code Segment and Data Segment Rights details for inputting to GDTFill function
;
%define CS_ACCES 10011011b ; CS and DS Access Rights (Details in GDT.INC)
%define DS_ACCES 10010011b

;
;  16 Bit Addressing initially
;
[bits 16]

;
; Code begins at 0x7c00
;
[org 0x7c00]

;
; Bios Jumps to 0xf000:0xffff
; Then it loads the first 512 bytes (BootSector) 
;  from first boot device to 0x0000:0x7c00
;

jmp boot


;
; Includes
;
%include "GDT.INC"


;
; Define Stack 
;
boot:
mov ax,0x07C0 
mov ds,ax
mov es,ax
mov ax,0x8FFF 
mov ss,ax


;
; Stack begins at 0xf000 and fills from there downwards
;
mov sp,0xFFFF 


;
; Note:
; Linear Address = Shift Segment by 1 byte and add Offset to it
;
; Read Kernel From Floppy to Memory location es:bx (0x1000 here)
; Cylinder Head Sector and Buffer are as follows:
; 
; es:bx - buffer where to load Kernel to
; ch    - track number
; cl    - starting sector
; dh    - head number
; dl    - drive number (0 here)
; Then call interrupt 0x13 
; 

xor ax,ax
int 0x13

;
; Do the floppy int 13 reading
;
push es 

mov ax,0x100
mov es,ax
mov bx,0

mov ah,2
mov al,30
mov ch,0
mov cl,2
mov dh,0
mov dl,0
int 0x13

;
; Now es holds stack addresss
;
pop es

;
; Fill GDT
; Refer to GDT.INC for details
;
GDTFill 0, 0xFFFFF,CS_ACCES,1101b,gdt_cs
GDTFill 0, 0xFFFFF,DS_ACCES,1101b,gdt_ds

;
; Store Limit of GDT beginning at location marked as gdtptr
; This has to be passed on to lgdt instruction
;
mov ax, gdtend
mov bx, gdt
sub ax,bx
mov word [gdtptr], ax

;
; Store Linear address of GDT at gdtptr after allowing space for the previous data
; Linear Address = Shift Segment by 1 byte and add Offset to it
;
xor ax,ax 
mov ax,ds 
mov bx,gdt
call LinearAdd
mov dword [gdtptr+2], ecx 

;
; Load gdt using lgdt. Disable interrupts before that
;
cli
lgdt[gdtptr]

;
; Move to protected mode. Set cr0's first bit to 1 by or'ing it
;
mov eax,cr0
or ax,1
mov cr0,eax

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Once in Protected Mode,
; Except cs all are defined w.r.t DataSegment
; DataSegment Descriptor from beginning of GDT is 8 bytes
; CodeSegment Descriptor from beginning of GDT is 10 bytes
; Stack (Very important! - This is what i messed with initially:
; --------------------------------------------------------------
; Defined w.r.t Data segment
; Beginning - 0x9f000 (Fills downwards)
; Stack size is  = 0x9f000 - DataSegment Value (= 0x0) (Have to change this)
; Quite enough for some small operations and LIBC Functions
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
jmp next

next:
mov ax,0x10
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax 

mov esp,0x9F000 ; Stack begins filling at this address

;
; protected mode segmented address = cs:0x1000
; This is nothing but 0x0:0x1000 in protected mode (Have to change these)
; Which is where kernel was loaded earlier from floppy
;
jmp dword 0x8:0x1000

end:
jmp end ; Just in case it slips the earlier step !

;
; Initially fill GDT with 0's
; 
gdt:
gdt_null:
dw 0,0,0,0
gdt_cs:
dw 0,0,0,0
gdt_ds:
dw 0,0,0,0
gdtend:


;
; The following is the GDT Pointer
; This is used for passing it on to LGDT Instruction
;
gdtptr:
dw 0x0000 ; 16 bit size of GDT
dd 0 ; 32 bit linear address of GDT


;
; Filling the rest of space with NOP
; Or else boot sector might start executing invalid instructions there
; Because of Junk Data
;
times 510-($-$$) db 144
;
; The signature of boot sector
;
dw 0xaa55 
GDT.INC中有以下内容:

; Calculate Linear Address
; Called with following Values:
;       ax - has segment number
;   bx - has offset number
; 
; Output:
;   ecx - has Linear Address
; 
; We are in 16 bit mode
;
; Linear Address = Shift Segment by 1 byte and add Offset to it
; So 0x07c0:0x0  = 0x7c00

LinearAdd:
    xor ecx,ecx
    mov cx,ax
    shl ecx,4
    and ebx,0x0000FFFF
    add ecx,ebx
    ret


; Filling Global Descriptor Table
; -------------------------------
;
; Note:
; -----
; 1. Order of variables input: Base (32 bits), 
;     Limit (20 bits), Access Rights(8 bits), Flags(4 bits), Segment Address 
;     (32 bits)
; 
; 2. Variables input to function are moved LS 4 bits First (Right to Left) to table
;
; 3. While reading LS 4 bits or MS 4 bits, read from left to right
;
; Base:
; -----
; Bits in Table          Bits in Field 'Base'        Location w.r.t Beginning of Segment
;---------------------------------------------------------------------------------------
; 16 - 31                0  - 15                     +2 (2 bytes in length)
; 32 - 39                16 - 23                     +4 (1 byte in length)
; 55 - 63                24 - 31                     +7 (1 byte in length)
;
;
; Howto:
; ------
;
; Below %5 ie., the fifth variable input to function GDTFill is Beginning of GDT's Code Segment
; [%5+2] represents entry 1 in above table
; [%5+4] represents entry 2 in above table
; [$5+7] represents entry 3 in above table
; Entry 1 is 2 bytes (so ax is moved to word [%5+2]
; Entry 2 and 3 are 1 byte (so al is moved to byte[%5+4] and [%5+7] respectively
;
; Limit:
; ------
;
; Bits in Table          Bits in Field 'Limit'        Location w.r.t Beginning of Segment
;---------------------------------------------------------------------------------------
; 0  -  15               0 -  15                       +0 (2 bytes in length)
; 16 -  20               48 - 51                       +6 (1 byte in length)
; 
; Refer 'Howto' above for detailed description
;
; Access Rights:
; --------------
; 
; Access Rights (Bit 40 to Bit 47) = Type (Bit 40 to Bit 43) + System Flag (Bit 44) + DPL (Bit 45 and 46) + Reserved (Bit 47); 
; Type        - Your Call (Say A is Kernel Code and B is User code (Here it is 11 and 3 respectively)
; System Flag - Both Code and Data Segment have S Flag = 1
; DPL         - Privilege level (Ring 0 or 3?) (Ring 0 - 00 and Ring 3 - 11)
; 
; Flags:
; ------
; G B O AVL
;
; G   - Granularity = 1 here (means Segment Size is 4096 bytes)
; B   - Address offsets used for accessing segments are 32 bits long
; O   - 0 (Don't know what it is!)
; AVL - 1 here (You Can Ignore it)
;
; 

%macro GDTFill  5   
    push eax
; Base
    mov eax,%1
    mov word [%5+2],ax
    shr eax,16         ; Shift Right to 
    mov byte [%5+4],al
    shr eax,8
    mov byte [%5+7],al
; Limit
    mov eax,%2
    and eax,0x000FFFFF
    mov word [%5],ax    ; ecrit (0..15)
    shr eax,16          ; place (16..19) sur le nibble inferieur
    mov byte [%5+6],0   ; initialise flags+lim(16..19) a 0
    or [%5+6],al        ; ecrit (16..19)
; flags :
    mov al,%4
    and al,0x0F
    shl al,4
    or [%5+6],al
; acces :
    mov byte [%5+5],%3
    pop eax
%endmacro
上述措施长期有效。然而,当我的内核开始变大时,数据段和代码段重叠了。虽然两者都从0开始,但数据段中的数据与代码段中的代码或类似的内容重叠。因此,我无法完全打印邮件

有没有一种方法可以改变数据段和代码段的基址,使得基址之间有一些空间,可以用二进制文件编写一个最大为1MB的小内核

我已将链接附加到内核,并在下面详细描述了该问题:

我使用以下方法制作内核:

make clean; make 
内部src文件夹

使用以下命令在qemu中引导它:

sudo qemu-system-i386 -net nic,vlan=0,model=pcnet -net tap,vlan=0,ifname=tap,script=no -fda ../flp/fileb.flp -boot a  -m 128
启动后,我运行以下命令来验证字符串操作:

testnum
然后我运行下面的命令,我看到消息在中间被剥离,而不是在某一行之后打印:

pcnetops
如果我在console.c中注释以下几行,并运行pcnetops,我将打印所有内容:

print( "sizeof(char) == ");
print(htos(sizeof(char)));
print(CRLF);

print("htos(stoh(ffffffff, LEFT_TO_RIGHT)): ");
print(htos(stoh((unsigned char *) "ffffffff", LEFT_TO_RIGHT)));
print(CRLF);

这就是为什么我怀疑我是否应该将代码段和数据段基址分开的原因,现在两者都是0

我在您的代码中看到了以下内容:

gdt_cs:
dw 0,0,0,0
gdt_ds:set
dw 0,0,0,0
您需要做的是修改描述符以包含当前设置为0的基。您可以将此图像用作指南:


通过适当地设置基和限制,可以确保CS和DS不会重叠。

问题在于int 13调用中没有读取所有软盘扇区。将al的值从30增加到40—读取然后再次编译的扇区数。现在它运行良好。我知道问题并没有以正确的方式完全解决:

可能有,但首先你必须弄清楚并告诉我们问题的细节,以及你如何编译/链接你认为太大或类似的代码。我不认为仅仅发布代码就很有帮助。嗨,Alexey,谢谢你看这些东西;这是我的代码:;请原谅我在许多领域的编码无知,因为我是非常新的低水平C编码;我正在通过教程学习一些东西。问题实际上是关于如何设置分段。您可能应该更改标题和标记以反映这一点。感谢Nathan,GDTFill 0,0xFFFFF,CS_ACCES,1101b,gdt_CS和GDTFill 0,0xFFFFF,DS_ACCES,1101b,gdt_DS行在代码中用于填充base和limitI的值。我尝试发送不同的值。读取某个地方,ax中可以存储的最大值为0xFFFF。因此,我可以在实模式中寻址最大0xFFFFF段0xFFFF和偏移量0xF。尝试了一些组合,但不起作用。不知道怎么回事你把两种模式搞混了。如果您处于实模式,则不需要GDT或任何段描述符。mov cs,ax将csbase设置为eax。最初,操作系统处于实模式。在执行mov cr0、eax之后,根据我的理解,它将更改为保护模式。因此,您需要做的是将GDT设置为实模式,然后在切换到保护模式后,您应该重新加载段,这一次将从GDT加载。在我看来,您的问题是如何在保护模式下设置段,以便它们不会重叠。如果你说的是实模式,那么它其实并不重要,因为你可以使用eax,它可以访问所有32位,即使段基是0。