JOS | linux 神秘的0xC0000000&&内核逻辑地址&&内核虚拟地址&&直接映射&&高端内存

0xC0000000:3GB的起始地址。一个进程分为两个部分:私有和全局。私有部分是指进程自己的代码,而全局部分则是指内核代码。局部是进程私有的,而全局则是所有进程公用的。理解这个只需要理解页表即可:4GB内存,0-3GB用于用户,3-4GB用于内核,也就是说AB两个进程,页表中0-3GB分别映射到了不同的物理内存,而3-4GB都是映射到了同一片物理内存即内核代码段(内核可以通过复制把内核部分页表项复制到用户进程的页表中)。(“《ULK》 P74翻译版“原话”:主内核全局目录的最高目录项部分作为参考模型,给每个普通进程对应的页全局目录项提供参考模型 。 ”)。

csdn博客-内核的连接脚本vmlinux.lds中

. = PAGE_OFFSET + TEXT_OFFSET; 将我们的内核代码编译到0xc0000000+TEXT_OFFSET处。就像我们做的嵌入式实验一样,编译时我们的代码默认会加载到0x30000000处执行一样,而启动代码刚开始是加载到0x0处,编译好的代码需要在地址0x0处执行一段,因为在把我们的代码复制到0x30000000之前代码中使用的都是相对跳转和相对偏移,并不涉及存放数据的地址,所以即使我们编译时设定执行地址是0x30000000而实际在地址0x0处执行并不会出错。(要想深刻理解偏移量,则可以尝试写一个程序,从实模式跳入保护模式,见《x86汇编语言:从实模式到保护模式》第十一章,当你犯够了错误才能理解了这个偏移量使用不当致错的原因,一旦理解,那么许多东西就很容易懂了)。

linux也一样,我们编译linux是编译到PAGE_OFFSET + TEXT_OFFSET处,而系统启动时并不是一开始就跳到0xC00000000以后的内核空间,而是会跳到固定地址(8086是FFFF:0000)处执行,然后再跳到内核空间。(PAGE_OFFSET:linux-2.6.0includeasm-i386page.h,i386中是0xc0000000

TEXT_OFFSET:linux-2.6.0archarmMakefile,其中定义了TEXTADDR 0xc0008000,可得出TEXT_OFFSET=0x8000)。linux-2.6.0archi386kernelhead.S中使用了三个变量,pg0,swapper_pg_dir,mmu_cr4_features,linux以后肯定会开启分页,所以这些变量的地址都是虚拟地址,而因为此时还未开启分页,所以又必须使用物理地址,所以我们会注意到在使用这三个变量时使用的都是  (var_addr - __PAGE_OFFSET)来获得变量地址,也就是说将虚拟地址直接减去一个固定的值就得到了其真正的物理地址,如《ULK》第三版P74倒数两行所说(他只是举了个例子说是8M):“分页第一个阶段的目标是允许在实模式和保护模式下(笔者注:此时肯定开启了分页)都很容易对这8MB寻址。因此内核必须创建一个映射,把从0x0~0x007fffff的线性地址和从0xc0000000~0xc07fffff的线性地址映射到从0x0~0x007fffff的物理地址”,所以说linux内核页表0xc0000000开始的一段线性地址空间必须映射到0x0处。因为32位的话linux内核只占据3GB到4GB这1GB的线性地址空间,所以需要留出一部分线性地址空间用做高端内存,即用做动态映射

内核逻辑地址&&内核虚拟地址个人笔记:

理解了0xc0000000,那么内核逻辑地址和内核虚拟地址就很好理解了。因为0xc0000000开始的一部分连续线性地址必须映射到物理地址0x0处开始的一段连续内存,那么这部分物理地址空间就可以看做一个段地址为0x0的段。举个例子,假设有线性地址0xc09110111,采用的是直接映射,于是便可以得到其对应的物理地址为0x00911011(0x00911011=0xc09110111-0xc00000)因为段基址为0,那么这个0x009110111便可以看做是段内偏移,而X86系列的段式内存管理中把这个段内偏移叫做逻辑地址,其实际地址=段地址(0)+段内偏移=段内偏移,也就是说可以把这个采用直接映射的与物理地址一 一对应的线性地址部分看做是以0x0为段基址的段的一个段内偏移(线性地址减去一个固定值便可得到,这个固定值就是0xC0000000),所以给这段线性地址重新取个名字,叫逻辑地址,所以说linux的内核逻辑地址一定是内核虚拟地址,而因为linux中有一部分线性地址(比如高端内存)不是采用直接映射的方式,.假设有个地址0xCfff0000不属于直接映射的线性地址范围,其映射的物理地址是随机的不等于段地址(0xC)+(0xCfff0000),也就是不可以把线性地址0xCfff0000看做段内偏移,所以说内核虚拟地址不一定是内核逻辑地址。

高端内存个人笔记:

假设PC安装了2G内存

因为linux中一个进程分为3GB用户空间和1GB内核空间,即线性地址0xc00000000xffffffff,故内核最多只能访问0xffffffff-0xc0000000即1GB内存。linux将0xC0000000开始的896MB虚拟地址直接映射到物理地址00x896MB处,然后当假设内核需要访问大于1Gb的物理地址时便将剩下的128M左右的虚拟地址空间映射都你想要访问的物理地址。举个例子,内核想要访问0x40000000(1GB)~0x40000000+X之间X M大小的物理地址空间时,便将0xf7000000~0xffffffff之间的一部分(假设从0xf9000000开始)线性地址即X M线性地址动态的映射到物理地址0x40000000~0x40000000+X,于是内核访问线性地址0xf9000000+Y便可以访问到物理地址0x40000000+Y(Y<X),也就是说临时映射,访问完了大于1Gb的物理地址后就释放这个临时的映射以便可以重复利用,也就是说当其他进程占据高端内存却没有释放时便会导致高端内存越来越少,以致当其他进程的内核部分想使用大于1GB的物理地址时无法办到

回弹缓冲区-百度百科:

DMA:设备使用的是物理地址,所以需要连续的物理页,所以高端内存除非映射到连续物理页,否则一般使用直接映射部分的地址空间做缓冲区

原文地址:https://www.cnblogs.com/cindycindy/p/13497131.html