有关linux内核地址的一个疑问

gjq_1988 2014-05-05 01:38:38
最近看linux内核启动过程, 有一点疑问想不明白,只好来求助了。
问题如下:
首先linux中,进程虚拟地址空间的3G-4G是内核空间,映射到物理地址的0地址。
并且 linux内核编译出来以后,可执行代码中的地址都是虚拟地址, 也就是说地址都大于0xc0000000, 目标是要内核映象在虚拟内核空间中运行, 这个也没问题。
我知道linux启动的时候,会开启分页功能,但在开启分页功能之前,仍会有一段代码运行在没有分页功能的状态下,这个时候linux内核代码中的指令不就无法正常寻址了吗?(因为分页没开启的时候线性地址就是物理地址,而内核并不在0xc0000000的位置上)
...全文
253 9 打赏 收藏 转发到动态 举报
写回复
用AI写文章
9 条回复
切换为时间正序
请发表友善的回复…
发表回复
gjq_1988 2014-05-09
  • 打赏
  • 举报
回复
引用 8 楼 yulinlang 的回复:
这里是相对短跳转,上面最后的JMP是绝对远跳转。
3Q!
yulinlang 2014-05-09
  • 打赏
  • 举报
回复
这里是相对短跳转,上面最后的JMP是绝对远跳转。
gjq_1988 2014-05-09
  • 打赏
  • 举报
回复
引用 6 楼 yulinlang 的回复:
  
  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<<6), BP_loadflags(%esi)
  94        jnz 2f
  95
  96/*
  97 * Set segments to known values.
  98 */
  99        lgdt pa(boot_gdt_descr)
 100        movl $(__BOOT_DS),%eax
 101        movl %eax,%ds
 102        movl %eax,%es
 103        movl %eax,%fs
 104        movl %eax,%gs
 105        movl %eax,%ss
 1062:
 107        leal -__PAGE_OFFSET(%ecx),%esp
 108
 109/*
 110 * Clear BSS first so that there are no surprises...
 111 */
 112        cld
 113        xorl %eax,%eax
 114        movl $pa(__bss_start),%edi
 115        movl $pa(__bss_stop),%ecx
 116        subl %edi,%ecx
 117        shrl $2,%ecx
 118        rep ; stosl
上面这段代码来自3.14.3的head_32.S,启动时,内核被加载道物理地址0x01000000处,这个地址是由.config文件里的CONFIG_PHYSICAL_START定义的。入口在startup_32处,可以看到,这里用pa宏将stack_start,boot_gdt_descr,__bss_start,__bss_stop等转换成了物理地址来访问, 然后内核会建立临时页表

 208page_pde_offset = (__PAGE_OFFSET >> 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宏了。
看了你的回复基本了解了,但还有一个小问题,这段代码里面有些地方用到了 汇编标号, 比如第94行 jnz 2f 这个2f 地址,不需要使用pa宏将它转为物理地址吗?
ketaotech 2014-05-08
  • 打赏
  • 举报
回复
这个问题大多数初学者都有类似的疑问。我大致回答一下,没有研究过内核启动,大致过程应该是正确的. 1.把内核加载到内存中 2.跳转到内核的加载地址处,这个跳转地址是0x10000这样的吧. 3.开始运行位置无关的代码,什么是位置无关的代码,看看汇编语言上都有介绍 4.把内核物理地址映射到0开始和0xc0000000开始两个位置 5.这时开不开分页单元都可以正常访问内核代码 6.内核初始化之后,就可以运行位置相关的代码了
yulinlang 2014-05-08
  • 打赏
  • 举报
回复
  
  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<<6), BP_loadflags(%esi)
  94        jnz 2f
  95
  96/*
  97 * Set segments to known values.
  98 */
  99        lgdt pa(boot_gdt_descr)
 100        movl $(__BOOT_DS),%eax
 101        movl %eax,%ds
 102        movl %eax,%es
 103        movl %eax,%fs
 104        movl %eax,%gs
 105        movl %eax,%ss
 1062:
 107        leal -__PAGE_OFFSET(%ecx),%esp
 108
 109/*
 110 * Clear BSS first so that there are no surprises...
 111 */
 112        cld
 113        xorl %eax,%eax
 114        movl $pa(__bss_start),%edi
 115        movl $pa(__bss_stop),%ecx
 116        subl %edi,%ecx
 117        shrl $2,%ecx
 118        rep ; stosl
上面这段代码来自3.14.3的head_32.S,启动时,内核被加载道物理地址0x01000000处,这个地址是由.config文件里的CONFIG_PHYSICAL_START定义的。入口在startup_32处,可以看到,这里用pa宏将stack_start,boot_gdt_descr,__bss_start,__bss_stop等转换成了物理地址来访问, 然后内核会建立临时页表

 208page_pde_offset = (__PAGE_OFFSET >> 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宏了。
gjq_1988 2014-05-08
  • 打赏
  • 举报
回复
引用 4 楼 fedorayang 的回复:
这个问题大多数初学者都有类似的疑问。我大致回答一下,没有研究过内核启动,大致过程应该是正确的. 1.把内核加载到内存中 2.跳转到内核的加载地址处,这个跳转地址是0x10000这样的吧. 3.开始运行位置无关的代码,什么是位置无关的代码,看看汇编语言上都有介绍 4.把内核物理地址映射到0开始和0xc0000000开始两个位置 5.这时开不开分页单元都可以正常访问内核代码 6.内核初始化之后,就可以运行位置相关的代码了
你好, linux 内核开启分页的代码是放在head.s 汇编文件里的, 这段汇编代码被链接到内核最开始的地方。 你说的意思是不是指 在head.s中,开启分页机制之前的代码, 是位置无关的,也就是说这段代码不会访问一个绝对地址,因此,这段代码可以运行在任意地址上,(因为这段汇编代码不会出现一个对大于0xc0000000的地址的绝对引用,所有的跳转或者引用都是相对于当前pc值的)
gjq_1988 2014-05-07
  • 打赏
  • 举报
回复
我上面提到过,内核编译后的可执行代码中引用的地址都是虚拟地址,也就是说地址都大于0xc0000000(3G), 目标是要内核映象在虚拟内核空间中运行。在 分页机制开启前这些地址是无效的(因为内核根本不在3G以上的位置,甚至系统内存可能根本就不到3G)。所以分页开启前这些地址不能直接被送到cpu的外部地址总线上,用于直接寻址对应的物理内存(因为可能没有那么大的内存,就算有,内核根本不在那个寻址的位置,内核此时实际上在物理内存的底部)。 试想一个系统本身可能只有1G内存,在分页开启前,怎么能访问3G以上的地址呢?
gjq_1988 2014-05-07
  • 打赏
  • 举报
回复
引用 1 楼 yq828 的回复:
当程序引用一个内存地址时,通过 把相应的段基址加到程序员看得见的逻辑地址上就形成了一个对应的线性地址, 此时若没有启用分页机制,则该线性地址就被送到cpu的外部地址总线上,用于直接寻址对应的物理内存。
我上面提到过,内核编译后的可执行代码中的地址都是虚拟地址,也就是说地址都大于0xc0000000, 目标是要内核映象在虚拟内核空间中运行。在 分页机制开启前这些地址是无效的。不能直接被送到cpu的外部地址总线上,用于直接寻址对应的物理内存。 试想一个系统本身可能只有1G内存,在分页开启前,怎么能访问3G以上的地址呢?
yq828 2014-05-07
  • 打赏
  • 举报
回复
当程序引用一个内存地址时,通过 把相应的段基址加到程序员看得见的逻辑地址上就形成了一个对应的线性地址, 此时若没有启用分页机制,则该线性地址就被送到cpu的外部地址总线上,用于直接寻址对应的物理内存。
Linux内核一直是学习的难点:将近3000万行代码,5万多个源文件,代码庞大繁杂、代码很难看懂。《Linux内核编程》将突破以往传统的学习方式,采取更有效和科学的学习方法,多角度地对内核进行多层次分析,不局限于形式,不拘泥细节,目的只有一个:更轻松、更高效地去理解内核、学习内核。为了更好地让学员掌握内核编程技能,更好地理解内核,本课程将采用并不局限于以下学习方法进行课程的录制:降维分析,化简为繁,将复杂的系统简单化用软件工程的方法分析内核:软件分层、模块化分解、框架迭代多角度立体分析Linux内核,目的只有一个:更好地理解内核利用Linux内核中的面向对象编程思想去分析复杂的子系统、子系统交互利用多任务编程的思想去分析Linux内核本套课程预计分为20个左右的小模块,每个模块一个专题,每个专题会陆续发布。拟录制的模块包括但不限于:模块机制、内核裁剪与配置、内核编译与启动、系统调用、中断、文件系统、调度、内存管理、内核同步、设备模型、字符驱动、块驱动、定时器、input、platform设备驱动、device tree、proc、sysfs、I/O...  本课程是《Linux内核编程》的入门篇,主要给大家介绍一下Linux内核开发、Linux驱动开发的就业行情、行业生态、需要掌握哪些技能、Linux内核的学习方法、如何搭建Linux内核的学习开发环境。 

4,436

社区成员

发帖
与我相关
我的任务
社区描述
Linux/Unix社区 内核源代码研究区
社区管理员
  • 内核源代码研究区社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧