6-acpi_and_apic.md (#27)
[lunaix-os.git] / docs / tutorial / 2-setup_gdt.md
1 ## 准备工作
2
3 ```sh
4 git checkout fedfd71f5492177a7c7d7fd2bd1529a832106395
5 ```
6
7 观看相应视频
8
9 ## 代码分析
10
11 这个commit实现了分段。
12
13 libs/libc/string下面是一些字符串操作函数,比较基础,所以略过。
14
15 主要分析boot.S和gdt.c中的代码
16
17 **为了分段,我们需要准备GDT,还要使用GDTR存放GDT的入口地址。**
18
19 `gdt.c`中负责构造好GDT。
20
21 ```c
22 #define GDT_ENTRY 5
23
24 uint64_t _gdt[GDT_ENTRY];
25 uint16_t _gdt_limit = sizeof(_gdt);
26 ```
27
28 GDT实际就是数组,一个单位(段描述符)是64bits。所以数组类型是`uint64_t`。_gdt_limit是GDT的大小。
29
30 ```c
31 void _set_gdt_entry(uint32_t index, uint32_t base, uint32_t limit, uint32_t flags) {
32     _gdt[index] = SEG_BASE_H(base) | flags | SEG_LIM_H(limit) | SEG_BASE_M(base);
33     _gdt[index] <<= 32;
34     _gdt[index] |= SEG_BASE_L(base) | SEG_LIM_L(limit);
35 }
36 ```
37
38 `_set_gdt_entry`函数负责自动地构造GDT的一个entry(段描述符)。根据资料,参数base要分成两端,保存到56-63bits(`SEG_BASE_H(base)`)和16-39bits(`SEG_BASE_M(base)`、`SEG_BASE_L(base)`)的位置[1]。limit保存到48-51bits(`SEG_LIM_L(limit)`)的位置。index表示设置第几号描述符。参数flags稍后分析。
39
40 ```c
41 void
42 _init_gdt() {
43     _set_gdt_entry(0, 0, 0, 0);
44     _set_gdt_entry(1, 0, 0xfffff, SEG_R0_CODE);
45 ```
46
47 第0号entry默认为空,第1号的base为0、limit为0xfffff。这种分段模式叫做平坦模式。一个指令需要访问一个数据,地址设为x。需要先通过ds得到一个段描述符(base为0、limit为0xfffff)。因为标志位G为1,所以要在limit的20位的后面补充`fff`得到真实的地址范围上限,即x取值范围是`0x0`-`0xffffffff`。再通过0+x得到最后的地址。可以发现这个范围会映射到32位的所有内存地址。
48
49 flags为`SEG_R0_CODE`(权限为ring0的代码段)。
50
51 ```c
52 #define SEG_R0_CODE         SD_TYPE(SEG_CODE_EXRD) | SD_CODE_DATA(1) | SD_DPL(0) | \
53                             SD_PRESENT(1) | SD_AVL(0) | SD_64BITS(0) | SD_32BITS(1) | \
54                             SD_4K_GRAN(1)
55 ```
56
57 这些标志位都可以查看资料了解,`SD_DPL(0)`表示该段权限为ring0。
58
59 接下来使用GDTR存放GDT的入口地址
60
61 ```assembly
62 .section .text
63     .global start_
64     .type start_, @function     /* Optional, this just give the 
65                                  * linker more knowledge about the label 
66                                  */
67     start_:
68         movl $stack_top, %esp
69         /* 
70             TODO: kernel init
71                 1. Load GDT
72                 2. Load IDT
73                 3. Enable paging
74         */
75         call _kernel_init
76         
77         subl $0x6, %esp
78         movl $_gdt, 2(%esp)
79         movw _gdt_limit, %ax
80         movw %ax, (%esp)
81         lgdt (%esp)
82         addl $0x6, %esp
83 ```
84
85 使用lgdt来设置GDTR的值
86
87 `lgdt`指令的操作数为6字节,2个低位字节为GDT的大小减1,4个高位字节为GDT的32位地址。
88
89 在`_kernel_init`调用后,`subl $0x6, %esp`抬高了栈顶,得到6字节的位置。然后把`$_gdt`、`_gdt_limit`保存到栈上。
90
91 ```assembly
92         movw $0x10, %cx
93         movw %cx, %es
94         movw %cx, %ds
95         movw %cx, %fs
96         movw %cx, %gs
97         movw %cx, %ss
98 ```
99
100 es等寄存器指向2号描述符(ring0数据段)
101
102 利用retf把0x8写入cs寄存器,让cs指向ring0代码段
103
104 ```assembly
105         pushw $0x08
106         pushl $_after_gdt
107         retf
108
109     _after_gdt:
110         pushl %ebx
111         call _kernel_main
112
113         cli
114     j_:
115         hlt
116         jmp j_
117 ```
118
119 ## 参考
120
121 [1]https://wiki.osdev.org/Global_Descriptor_Table