Series catalog
- Preface
- Preparation work
- BIOS boot to real mode
- GDT and protected mode
- A preliminary
- load and enter the kernel
- display and print
- Global Descriptor Table GDT
- Interrupt handling
- virtual memory perfect
- implements heap and malloc
- First kernel thread
- multi-thread switching
- lock and multi-thread synchronization
- Enter user mode
- process
- system call
- Simple file system
- Load executable program
- keyboard driver
- run shell
Extend and reload GDT
In this article, we will redefine and expand the global descriptor table GDT in the kernel and load it again. The content of this article will also be relatively simple, and it is more about consulting and familiarizing with x86 related manual documents.
We have preliminarily defined and loaded GDT once in the loader
stage, where we only defined the code
and data
segments of the kernel, because so far, and for a long period of time later, we have always been in the kernel space. Privilege level 0 runs. But as an OS, it is ultimately necessary to run and manage user programs, so the user mode code
and data
sections need to be added to the GDT.
In addition, we also hope to rearrange the previous GDT, after all, it is rather chaotic under assembly, and many data structures are not clear to manage.
Create GDT
GDT
and segment
is a historical legacy of the x86 architecture, which is very annoying. But for historical compatibility, Intel has to always retain these historical baggages. We don't have to spend too much thought and brains on this, just follow the document specification, fill in all that should be filled in, write all that should be written, and just take it lightly. It is not a core part of our project.
By convention, the code link is given first. The main source file is src/mem/gdt.c .
For GDT documentation, you can refer to here .
First, we need to define the data structure of the GDT entry:
struct gdt_entry {
uint16 limit_low;
uint16 base_low;
uint8 base_middle;
uint8 access;
uint8 attributes;
uint8 base_high;
} __attribute__((packed));
typedef struct gdt_entry gdt_entry_t;
It corresponds to such a 64 bit structure:
Among them, base refers to the memory base address of the segment, and limit is the length, which can have two units of 1 or 4KB.
The rest are some of the flag bits shown in Figure 2, so I don't need to spend much time here, and I still need to proofread the document carefully.
Then we define the GDT table:
static gdt_entry_t gdt_entries[7];
We have allocated 7 entries here:
- Item 0 is reserved;
- The first one is
kernel
ofcode segment
; - The second one is
kernel
ofdata segment
; - The third is the video segment, this is not necessary and can be ignored;
- The fourth is
user
ofcode segment
; - The fifth is
user
ofdata segment
; - The sixth is
tss
;
From the fourth onwards, all user mode needs to be used. The sixth one, tss
present, we will come back and take a closer look at this part when we enter the user mode later.
Then we define the function to set the GDT entry:
static void gdt_set_gate(
int32 num, uint32 base, uint32 limit, uint8 access, uint8 flags) {
gdt_entries[num].limit_low = (limit & 0xFFFF);
gdt_entries[num].base_low = (base & 0xFFFF);
gdt_entries[num].base_middle = (base >> 16) & 0xFF;
gdt_entries[num].access = access;
gdt_entries[num].attributes = (limit >> 16) & 0x0F;
gdt_entries[num].attributes |= ((flags << 4) & 0xF0);
gdt_entries[num].base_high = (base >> 24) & 0xFF;
}
Just look at the picture above.
Set these entries in the GDT table:
// kernel code
gdt_set_gate(1, 0, 0xFFFFF, DESC_P | DESC_DPL_0 | DESC_S_CODE | DESC_TYPE_CODE, FLAG_G_4K | FLAG_D_32);
// kernel data
gdt_set_gate(2, 0, 0xFFFFF, DESC_P | DESC_DPL_0 | DESC_S_DATA | DESC_TYPE_DATA, FLAG_G_4K | FLAG_D_32);
// video: only 8 pages
gdt_set_gate(3, 0, 7, DESC_P | DESC_DPL_0 | DESC_S_DATA | DESC_TYPE_DATA, FLAG_G_4K | FLAG_D_32);
// user code
gdt_set_gate(4, 0, 0xBFFFF, DESC_P | DESC_DPL_3 | DESC_S_CODE | DESC_TYPE_CODE, FLAG_G_4K | FLAG_D_32);
// user data
gdt_set_gate(5, 0, 0xBFFFF, DESC_P | DESC_DPL_3 | DESC_S_DATA | DESC_TYPE_DATA, FLAG_G_4K | FLAG_D_32);
Comparing kernel
and user
, there are two main points:
Access Byte
inPrivl
: There are two bits in total. Forkernel
, it is00
, and foruser
it is11
, which meansDPL (Descriptor Privilege Level)
, which represents the minimum CPU privilege level required to access this segment.Limit
: Because the user space 3GB in the following limits, so itLimit
is0xBFFFF
, noteFlags
theGr (Granularity)
bit is 1,Limit
units are4KB
, can be calculated(0xBFFFF + 1) * 4KB = 3GB
;
With these two limitations, when the CPU is in the user mode, it cannot access the kernel space above 3GB, so the segment mechanism is brought into play.
Reload GDT
The new GDT is ready, the next step is to reload it, the code is src/mem/gdt_load.S .
load_gdt:
mov eax, [esp + 4]
lgdt [eax]
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov ss, ax
mov ax, 0x18
mov gs, ax
jmp 0x08:.flush
.flush:
ret
Where load_gdt
is declared in the C source file as follows:
extern void load_gdt(gdt_ptr_t*);
The parameter is the GDT pointer:
struct gdt_ptr {
uint16 limit;
uint32 base;
} __attribute__((packed));
typedef struct gdt_ptr gdt_ptr_t;
Load the GDT table with the instruction lgdt
, and then give each data
segment register, pointing to the kernel data
segment, the offset is 0x10
, because it is the second entry in the GDT table.
Then use a far jmp instruction jmp 0x08:.flush
to refresh the cs
register to point to segment kernel code
Note 0x08
because kernel code
segment is the first entry in the GDT.
OK, now the new GDT is loaded.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。