当前位置: 技术问答>linux和unix
有关linux内核地址的一个疑问
来源: 互联网 发布时间:2017-05-17
本文导语: 最近看linux内核启动过程, 有一点疑问想不明白,只好来求助了。 问题如下: 首先linux中,进程虚拟地址空间的3G-4G是内核空间,映射到物理地址的0地址。 并且 linux内核编译出来以后,可执行代码中的地址都是虚拟地址, ...
最近看linux内核启动过程, 有一点疑问想不明白,只好来求助了。
问题如下:
首先linux中,进程虚拟地址空间的3G-4G是内核空间,映射到物理地址的0地址。
并且 linux内核编译出来以后,可执行代码中的地址都是虚拟地址, 也就是说地址都大于0xc0000000, 目标是要内核映象在虚拟内核空间中运行, 这个也没问题。
我知道linux启动的时候,会开启分页功能,但在开启分页功能之前,仍会有一段代码运行在没有分页功能的状态下,这个时候linux内核代码中的指令不就无法正常寻址了吗?(因为分页没开启的时候线性地址就是物理地址,而内核并不在0xc0000000的位置上)
问题如下:
首先linux中,进程虚拟地址空间的3G-4G是内核空间,映射到物理地址的0地址。
并且 linux内核编译出来以后,可执行代码中的地址都是虚拟地址, 也就是说地址都大于0xc0000000, 目标是要内核映象在虚拟内核空间中运行, 这个也没问题。
我知道linux启动的时候,会开启分页功能,但在开启分页功能之前,仍会有一段代码运行在没有分页功能的状态下,这个时候linux内核代码中的指令不就无法正常寻址了吗?(因为分页没开启的时候线性地址就是物理地址,而内核并不在0xc0000000的位置上)
|
这个问题大多数初学者都有类似的疑问。我大致回答一下,没有研究过内核启动,大致过程应该是正确的.
1.把内核加载到内存中
2.跳转到内核的加载地址处,这个跳转地址是0x10000这样的吧.
3.开始运行位置无关的代码,什么是位置无关的代码,看看汇编语言上都有介绍
4.把内核物理地址映射到0开始和0xc0000000开始两个位置
5.这时开不开分页单元都可以正常访问内核代码
6.内核初始化之后,就可以运行位置相关的代码了
1.把内核加载到内存中
2.跳转到内核的加载地址处,这个跳转地址是0x10000这样的吧.
3.开始运行位置无关的代码,什么是位置无关的代码,看看汇编语言上都有介绍
4.把内核物理地址映射到0开始和0xc0000000开始两个位置
5.这时开不开分页单元都可以正常访问内核代码
6.内核初始化之后,就可以运行位置相关的代码了
|
80/*
81 * 32-bit kernel entrypoint; only used by the boot CPU. On entry,
82 * %esi points to the real-mode code as a 32-bit pointer.
83 * CS and DS must be 4 GB flat segments, but we don't depend on
84 * any particular GDT layout, because we load our own as soon as we
85 * can.
86 */
87__HEAD
88ENTRY(startup_32)
89 movl pa(stack_start),%ecx
90
91 /* test KEEP_SEGMENTS flag to see if the bootloader is asking
92 us to not reload segments */
93 testb $(1 20);
209
210 movl $pa(__brk_base), %edi
211 movl $pa(initial_page_table), %edx
212 movl $PTE_IDENT_ATTR, %eax
21310:
214 leal PDE_IDENT_ATTR(%edi),%ecx /* Create PDE entry */
215 movl %ecx,(%edx) /* Store identity PDE entry */
216 movl %ecx,page_pde_offset(%edx) /* Store kernel PDE entry */
217 addl $4,%edx
218 movl $1024, %ecx
21911:
220 stosl
221 addl $0x1000,%eax
222 loop 11b
223 /*
224 * End condition: we must map up to the end + MAPPING_BEYOND_END.
225 */
226 movl $pa(_end) + MAPPING_BEYOND_END + PTE_IDENT_ATTR, %ebp
227 cmpl %ebp,%eax
228 jb 10b
229 addl $__PAGE_OFFSET, %edi
230 movl %edi, pa(_brk_end)
231 shrl $12, %eax
232 movl %eax, pa(max_pfn_mapped)
233
234 /* Do early initialization of the fixmap area */
235 movl $pa(initial_pg_fixmap)+PDE_IDENT_ATTR,%eax
236 movl %eax,pa(initial_page_table+0xffc)
把0x00000000一直到内核结束这部分的物理地址分别映射到从0x00000000和从0xC0000000开始的对应虚拟地址,打个比方说,虚拟地址0x00000001和0xC0000001都映射到同样的物理地址0x00000001处。
最后开启分页
387/*
388 * Enable paging
389 */
390 movl $pa(initial_page_table), %eax
391 movl %eax,%cr3 /* set the page table pointer.. */
392 movl $CR0_STATE,%eax
393 movl %eax,%cr0 /* ..and set paging (PG) bit */
394 ljmp $__BOOT_CS,$1f /* Clear prefetch and normalize %eip */
3951:
396 /* Shift the stack pointer to a virtual address */
397 addl $__PAGE_OFFSET, %esp
可以看到,直到最后开启分页前,还是用pa宏来访问物理地址,当开启分页后,内核会用一个长跳转跳转到内核空间,这时候,因为对应的页表已经建立,可以使用虚拟地址了,后面也就不需要使用pa宏了。