初始化阶段1包括从内核入口1MB
处的startup_32
到公共的start_kernel
函数
=============================== arch/i386/kernel/head.S =================================
50 /*
51 * 32-bit kernel entrypoint; only used by the boot CPU. On entry,
52 * %esi points to the real-mode code as a 32-bit pointer.
53 * CS and DS must be 4 GB flat segments, but we don't depend on
54 * any particular GDT layout, because we load our own as soon as we
55 * can.
56 */
57 ENTRY(startup_32)
58
59 /*
60 * Set segments to known values.
61 */
62 cld
63 lgdt boot_gdt_descr - __PAGE_OFFSET
64 movl $(__BOOT_DS),%eax
65 movl %eax,%ds
66 movl %eax,%es
67 movl %eax,%fs
68 movl %eax,%gs
在内核的链接脚本中指定了startup_32
是程序入口而且代码段从__PAGE_OFFSET
+ 0x100000
处开始,所以代码中的符号地址都是高地址(3GB+1MB
以上),而内核代码运行时被加载到低地址1MB
,此时是实模式,代码中访问的地址要用物理地址。所以lgdt
要在符号地址boot_gdt_descr
上减去__PAGE_OFFSET
。
=============================== arch/i386/kernel/head.S =================================
450 boot_gdt_descr:
451 .word __BOOT_DS+7
452 .long boot_gdt_table - __PAGE_OFFSET
boot_gdt_descr
处是要存放到gdtr
的值,第一个.word
表示gdt
的长度-1
,接下来的.long
表示gdt
的起始地址。
================================ include/asm/segment.h ==================================
83 /* Simple and small GDT entries for booting only */
84
85 #define GDT_ENTRY_BOOT_CS 2
86 #define __BOOT_CS (GDT_ENTRY_BOOT_CS * 8)
87
88 #define GDT_ENTRY_BOOT_DS (GDT_ENTRY_BOOT_CS + 1)
89 #define __BOOT_DS (GDT_ENTRY_BOOT_DS * 8)
CS
段描述符在第2条,DS
在第3条,__BOOT_DS
的值为24,所以bootgdt的大小就是24+7。
=============================== arch/i386/kernel/head.S =================================
471 .align L1_CACHE_BYTES
472 ENTRY(boot_gdt_table)
473 .fill GDT_ENTRY_BOOT_CS,8,0
474 .quad 0x00cf9a000000ffff /* kernel 4GB code at 0x00000000 */
475 .quad 0x00cf92000000ffff /* kernel 4GB data at 0x00000000 */
段描述符表的内容。.fill
标记的意思是产生GDT_ENTRY_BOOT_CS
条(2条)长度为8内容为0的条目。boot gdt总共4条,CS
和DS
都是4G范围的平坦映射。
接着回到startup_32
,把__BOOT_DS
段选择子赋给ds
、es
、fs
、gs
(没有ss
),它们会刷新自己的影子寄存器。
=============================== arch/i386/kernel/head.S =================================
70 /*
71 * Clear BSS first so that there are no surprises...
72 * No need to cld as DF is already clear from cld above...
73 */
74 xorl %eax,%eax
75 movl $__bss_start - __PAGE_OFFSET,%edi
76 movl $__bss_stop - __PAGE_OFFSET,%ecx
77 subl %edi,%ecx
78 shrl $2,%ecx
79 rep ; stosl
清除内存上.bss
段所指的范围。bss
段是未初始化的全局变量和初始值为0的全局变量,它们在elf
文件中只需要存放相应的符号名称,不存放那些0
(初始值不为0
的变量当然需要在二进制文件中存放它的初始值)。在加载的时候要分配空间给这些变量,并初始化为0
。
=============================== arch/i386/kernel/head.S =================================
81 /*
82 * Initialize page tables. This creates a PDE and a set of page
83 * tables, which are located immediately beyond _end. The variable
84 * init_pg_tables_end is set up to point to the first "safe" location.
85 * Mappings are created both at virtual address 0 (identity mapping)
86 * and PAGE_OFFSET for up to _end+sizeof(page tables)+INIT_MAP_BEYOND_END.
87 *
88 * Warning: don't use %esi or the stack in this code. However, %esp
89 * can be used as a GPR if you really need it...
90 */
91 page_pde_offset = (__PAGE_OFFSET >> 20);
92
93 movl $(pg0 - __PAGE_OFFSET), %edi
94 movl $(swapper_pg_dir - __PAGE_OFFSET), %edx
95 movl $0x007, %eax /* 0x007 = PRESENT+RW+USER */
96 10:
97 leal 0x007(%edi),%ecx /* Create PDE entry */
98 movl %ecx,(%edx) /* Store identity PDE entry */
99 movl %ecx,page_pde_offset(%edx) /* Store kernel PDE entry */
100 addl $4,%edx
101 movl $1024, %ecx
102 11:
103 stosl
104 addl $0x1000,%eax
105 loop 11b
106 /* End condition: we must map up to and including INIT_MAP_BEYOND_END */
107 /* bytes beyond the end of our own page tables; the +0x007 is the attribute bits */
108 leal (INIT_MAP_BEYOND_END+0x007)(%edi),%ebp
109 cmpl %ebp,%eax
110 jb 10b
111 movl %edi,(init_pg_tables_end - __PAGE_OFFSET)
va
右移20
位就是该地址在页目录表中表项偏移,page_pde_offset
就是内核代码高地址空间的起始页目录项偏移。
pg0
是在下面的链接脚本中定义,是bss
段之后的第一个页地址,链接