您好、欢迎来到现金彩票网!
当前位置:一品彩票app下载 > 共享页表 >

进程的虚拟地址和内核中的虚拟地址有什么关系?

发布时间:2019-05-11 15:38 来源:未知 编辑:admin

  进程的虚拟地址在内核中通过三级页表到达物理地址。 而内核的虚拟地址在NORMAL部分算是逻辑地址只是线性的映射。 这两者有什么关系么?或者说内核态为什么还要有虚拟地址存在?

  1. 现在 Linux 内核是4级页表结构,3级页表的时代是10年前了。 X86_64 架构下,无论 Intel 还是 AMD 的 CPU, 都是四级的硬件页表,所以软件层面的页表至少要4级(否则,进程访问的空间将受限, 因为有一级页表被固定住了,所以3级页表时代,X86_64 只能访问 512GB 空间, 而 X86_64 的设计可访问空间达到 131 072( = 2^47) GB。打个比方就是,省,市,区,县 四级行政规划,硬要嵌套进三级规划,只能表达市,区,县三级,省一级给固定住了, 访问范围缩小了)。

  2. 不过你会问:i386 只有三级硬件页表:PUD - PMD - PTE, 怎么嵌入四级软件页表结构? 答案就是虚设一层。打个比方:北京是省级行政单位,如果要按省,市,区,县结构来表达某县,就是: 北京(省)北京(市)XX区XX县, 有一层完全就是占个位而已。有兴趣了解 Linux 页表的变迁历史,可以看我之前写过的文章:Linux内核4级页表的演进

  3. 内核空间,用户空间的地址都是虚拟地址,都要经过 MMU 的翻译,变成物理地址。用户空间的虚拟地址,就是按前面所述的走四级页表来翻译。 内核空间虚拟地址是所有进程共享的,重要的是,从效率角度看, 如果同样走四级页表翻译的流程,速度太慢;于是,内核在初始化时,就创建内核空间的映射(因为所有进程共享,有一份就够了),并且,采用的就是线性映射,而不是走页表翻译这种类似哈希表的方式。这样,内核地址的翻译,简化为一条偏移加减指令就行,相比走页表,效率大大提高(不过,内核空间并非完全不用页表,此处讲原理所以简化,详细的看尾注).

  4. 至于为什么用户空间不能也像内核空间这么做,原因是用户地址空间是随进程创建才产生的,它的页面可能散布在不同的物理内存中,无法这么做。另外,走页表的过程,不止是翻译的过程,还是一个权限检查的过程,对于不可控的用户态地址,这安全性检查必不可省。而内核空间,只有一份,所有可以提前固定下来一片连续的物理地址空间,按线性方法来映射。这是很正常的优化方法。

  5. 那么问题来了,在 Linux 刚引入的时候, i386 4G 的进程空间典型的是 3G user + 1G kernel 的划分,这教科书上都有说。 那按前面的线G 内核空间,只能映射 1G 物理地址空间,这对内核来说,太掣肘了。所以,折衷方案是, Linux 内核只对 1G 内核空间的前 896 MB 按前面所说的方法线 MB 的内核空间, 采用动态映射[1]的方式,即按需映射的方式 ,这样,内核态的访问空间更多了。 这个直接映射的部分, 就是题主所说的 NORMAL 区, 就是所谓低端内存。到了 64 位时代, 内核空间大大增大, 这种限制就没了,内核空间可以完全进行线]的缘故, 仍保留有动态映射这部分。

  [1] 动态映射不全是为了内核空间可以访问更多的物理内存,还有一个重要原因: 当内核需要连续多页面的空间时,如果内核空间全线性映射,那么,可能会出现内核空间碎片化而满足不了这么多连续页面分配的需求。基于此,内核空间也必须有一部分是非线性映射,从而在这碎片化物理地址空间上,用页表构造连续虚拟地址空间,这就是所谓vmalloc空间。

  针对楼主的问题,我从OS实现的角度做个补充(已有的答案都是从硬件实现的原理为出发点的)。

  首先的首先,开启分页机制后内核也不能绕过该机制,所以内核也要有虚拟地址。

  首先,进程的虚拟地址和内核的虚拟地址有一点不同:内核的虚拟地址如果触发了缺页中断整个系统就panic了,而进程的虚拟地址不是这样。为什么这样设计呢?这是因为如果不做此限制,内核上下文中触发缺页中断后进行中断处理时还可能继续发生嵌套的缺页中断,如此会一直嵌套。

  其次,怎么避免在内核上下文中不产生缺页中断呢?最简单的方法就是把所有的物理内存映射到内核的某段虚拟地址空间,从此段空间内malloc的虚拟地址都已经映射好了不会触发缺页中断。其他的内核的虚拟地址访问产生的缺页中断,肯定是代码错误或者是硬件错误,只能panic。而最简单的映射方法就是物理地址加上固定的偏移就得到虚拟地址,偷懒的做法

  最后,实际的OS的实现上(比如FreeBSD)不会按照4k的页大小把所有的物理地址都预先映射出来,因为这样需要浪费不少内存作为页表。实际上是按照2M(64位系统)的页做映射,称之为direct mapping,需要做4k页面映射的时候,除了分配出一页,还可能需要申请一页作为页表,并把对应的物理地址写入作为页表的这一页的对应PSN处。如果没有direct mapping,上述做法的实现会很复杂。

  进程地址,比如指针,call,jmp的地址,是跟段基值相关,如果段基值为1g,那么程序的地址=线g。但几乎所有的操作系统都把段基值设置成0,这样就是程序看到的地址跟操作系统的地址是一样的。另外,不同的进程操作系统一般做隔离,把整个线部分,一部分所有进程共享,操作系统,设备驱动,共享内存分这里。另外一部分属于进程空间私有,每个进程私有空间的对应物理内存是不同的。这个通过页表映射实现,即每个进程有独立的页表映射,当然共享部分映射是一样的,页表映射也是共享的,只需要进程私有空间部分另外映射。进程切换的时候,需要切换页表,性能很低。同一个进程的线程切换则不用。

  (逻辑地址有时也被叫虚拟地址) 都是位于 0x00000000~0xFFFFFFFF 这段虚拟地址空间 ,其中用户空间逻辑地址 位于

  0x00000000~ 0xBFFFFFFF ,共3g , 内核逻辑地址是 0xC0000000~0XFFFFFFFF,共1g。而且这个地址空间对于每个进程来说都是独立的。

  每个进程看到的 地址空间都是一样的,比如.text 都是从0x80048000 开始,然后用户栈都是从0xBFFFFFFF 向低地址增长,内核地址空间都是0xC0000000~0xFFFFFFFF。

  但是,每个进程的逻辑地址0x08048000 ~ system break 以及stack中对应的内容应该是不一样的(除非两个共享地址空间,那就是线程了)。 那问题来了,不同进程 有相同的逻辑地址,但是却又不同的内容,这怎么实现呢?

  了。每个进程都有一个自己的页表,使得 某逻辑地址对应于某个物理内存。 正因为 每个进程都有一个自己的页表,使得相同的逻辑地址映射到 不同的物理内存。对于线程 ,它也有自己的页表,只是页表的 逻辑地址 映射到的物理内存相同。

  那进程的页表是怎样的呢?首先,内核本身就有一个页表了,而且 对于normal_area 都是一一映射到物理内存的,具体可查一下网上资料关于低端内存和高端内存。 这里可以不用知道到底怎么映射,只需要知道 内核中有一个页表, 能把

  这个内核逻辑地址对于每一个进程来说都应该是一样的,所以 ,在创建进程表时候,就可以直接拷贝该内核的页表,作为该进程 的页表的一部分,另外对于 该进程的用户部分的页表,可以简单地理解为 把逻辑地址 映射到一个 空闲的 物理内存区域。

  每当切换到另一个进程时,就要设置这个进程的页表,通过 设置MMU的某些寄存器 ,然后 MMU 就可以把 cpu 发出的逻辑地址 转化为 物理地址了。

  虽然看起来 ,该进程 拥有 0x00000000~0xFFFFFFFF 的 逻辑地址空间 ,但是0xC0000000~0xFFFFFFFF 这段是内核的逻辑地址 ,在用户态时访问会出错,权限不够,如果想访问,需要切换到内核态 ,可以通过 系统调用等。系统调用代表某个进程运行于内核,此时,相当于该进程可以访问0xC0000000~0xFFFFFFFF 这个地址了(但实际上 只能访问 该进程的某个8KB的内核栈 ,这里不是很确定,因为每个进程都有自己独立的8KB的内核栈,你应该是不能访问别的内核的内核栈),此时可以把用户空间逻辑地址 在 内核逻辑地址 之间 进行内存拷贝。

  另外 0X00000000 ~0x08048000 是不能给用户访问的,这里面是一些C运行库的内容。访问会报segement fault 错误。

  另外linux 对只读的内容可以共享,在物理内存中只有一份拷贝。这样,即使在逻辑地址上看起来有很多c库等运行库在里面,但整个内存只有一份拷贝,当然,对于可写的数据段,每个进程都应该有独立一份。

  虚拟地址空间本来是为了用户个进程可以互相隔离开来了。内核的线性地址空间是固定映射物理内存的,所以它的虚拟地址与物理地址是一一对应并且对应关系永不改变的(不重启情况下)。所以理论上来讲,内核直接用物理地址也是可以的,虚拟地址对它来说意义不大(但是页式内存管理,页表的权限位对于内核还是有意义的)。

  然而单独让内核可以直接访问物理地址也没有什么好处,反而需要特殊的硬件处理这个特殊的情况,这没有什么必要。所以还是委屈一下内核,让内核也使用虚拟地址好了。

  所以说用户使用的虚拟地址和内核使用的虚拟地址有什么关系呢?实际上并没有什么关系,内核本不必使用虚拟地址的。

  MMU 开启以后,CPU要访问物理内存,需要把其地址经过MMU翻译成物理地址,通过总线访问物理内存。

  当MMU关闭后,可以简单认为MMU不存在了。CPU要访问物理内存就把地址直接放在总线上去访问物理内存。

  在没有开启页式地址转换之前, „内核的虚拟地址经过一次段地址转换就直接得到物理地址了。也就是:

  PA(物理地址) = LA(线性地址) = VA(虚拟/逻辑地址) - KERNBASE

  在页式地址转换开启之后,不管是用户还是内核,都是需要通过页表来找到物理地址。所以内核访问任意(=KERNBASE 的)虚拟地址 VA = KERNBASE - x , 都需要先经过一次段式转换得到 LA = VA - KERNBASE = x. 然后这个线性地址再通过页表得到物理地址。 而在页表中, KERNBASE开始的一段地址在页表中是直接映射到整个物理地址空间的,所以打开页式地址转换后内核使用虚拟地址KERNBASE+x访问到的正是物理地址x处的地址。 这和开启页式转换之前的线性映射结果相同。

  然后呢,如@SuperFranky所说,内核部分的页表是所有进程共享的。

  而内核的地址映射是线程映射。对于内核和进程,所见地址都是虚拟地址。MMU负责把虚拟地址转换成物理地址。

  给你一些名词就知道它们之间的关系 VIVT VIPT CACHE. TLB PGD. PMD PTE VMA ANONYMOUS MAP RBTree PSTree MMU

  如果是内存够大的话就可以全部线G给内核用,如果线性映射内核就访问不了所有内存,所有内核有一个high memory区用来映射所有的物理地址,所有说内存够大就可以全部线G的架构说实话并不了解,所有具体怎么映射请别人来回答

  内核的虚拟地址空间(3GB - 4GB) + 当前正在运行的进程虚拟地址空间(0-3GB) = 4GB

http://styleinch.com/gongxiangyebiao/187.html
锟斤拷锟斤拷锟斤拷QQ微锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷微锟斤拷
关于我们|联系我们|版权声明|网站地图|
Copyright © 2002-2019 现金彩票 版权所有