📄 linu家园-linux kernel核心中文手册.htm
字号:
<P> </P>
<P></P>
<P>3.1.5 Access Control(访问控制)</P>
<P> </P>
<P>页表条目也包括访问控制信息。当处理器使用页表条目将进程的虚拟地址映射到物理地址的时候,它很容易利用访问控制信息控制进程不要用不允许的方式进行访问。</P>
<P> </P>
<P>有很多原因你希望限制对于内存区域的访问。一些内存,比如包含执行代码,本质上是只读的代码,操作系统应该禁止进程写它的执行代码。反过来,包括数据的页可以写,但是如果试图执行这段内存应该失败。大多数处理器有两种执行状态:核心态和用户态。你不希望用户直接执行核心态的代码或者存取核心数据结构,除非处理器运行在核心态。
</P>
<P>访问控制信息放在PTE(page table entry)中,而且和具体处理器相关。图3.2显示了Alpha AXP的PTE。各个位意义如下:</P>
<P> </P>
<P>V 有效,这个PTE是否有效</P>
<P>FOE “Fault on Execute” 试图执行本页代码时,处理器是否要报告page fault,并将控制权传递给操作系统。</P>
<P>FOW “Fault on Write” 如上,在试图写本页时产生page fault</P>
<P>FOR “fault on read” 如上,在试图读本页时产生page fault</P>
<P>ASM 地址空间匹配。用于操作系统清除转换缓冲区中的部分条目</P>
<P>KRE 核心态的代码可以读本页</P>
<P>URE 用户态的代码可以读本页</P>
<P>GII 间隔因子,用于将一整块映射到一个转换缓冲条目而非多个。</P>
<P>KWE 核心态的代码可以写本页</P>
<P>UWE 用户态的代码可以写本页</P>
<P>Page frame number
对于V位有效的PTE,包括了本PTE的物理页编号;对于无效的PTE,如果不是0,包括了本页是否在交换文件的信息。</P>
<P> </P>
<P>以下两位由Linux定义并使用</P>
<P>_PAGE_DIRTY 如果设置,本页需要写到交换文件中。</P>
<P>_PAGE_ACCESSED Linux 使用,标志一页已经访问过</P>
<P> </P>
<P>3.2 Caches(高速缓存)</P>
<P>如果你用以上理论模型来实现一个系统,它可以工作,但是不会太高效率。操作系统和处理器的设计师都尽力让系统性能更高。除了使用更快的处理器、内存等,最好的方法是维护有用信息和数据的高速缓存,这会使一些操作更快。Linux使用了一系列和高速缓存相关的内存管理技术:</P>
<P> </P>
<P>Buffer Cache: Buffer cache
包含了用于块设备驱动程序的数据缓冲区。这些缓冲区大小固定(例如512字节),包括从块设备读出的数据或者要写到块设备的数据。块设备是只能通过读写固定大小的数据块来访问的设备。所有的硬盘都是块设备。块设备用设备标识符和要访问的数据块编号作为索引,用来快速定位数据块。块设备只能通过buffer
cache存取。如果数据可以在buffer cache中找到,那就不需要从物理块设备如硬盘上读取,从而使访问加快。</P>
<P>参见fs/buffer.c</P>
<P>Page Cache
用来加快对磁盘上映像和数据的访问。它用于缓存文件的逻辑内容,一次一页,并通过文件和文件内的偏移来访问。当数据页从磁盘读到内存中时,被缓存到page
cache中。</P>
<P>参见mm/filemap.c</P>
<P>Swap Cache
只有改动过的(或脏dirty)页才存在交换文件中。只要它们写到交换文件之后没有再次修改,下一次这些页需要交换出来的时候,就不需要再写到交换文件中,因为该页已经在交换文件中了,直接废弃该页就可以了。在一个交换比较厉害的系统,这会节省许多不必要和高代价的磁盘操作。</P>
<P>参见mm/swap_state.c mm/swapfile.c</P>
<P> </P>
<P> </P>
<P></P>
<P>Hardware
Cache:硬件高速缓存的常见的实现方法是在处理器里面:PTE的高速缓存。这种情况下,处理器不需要总是直接读页表,而在需要时把页转换表放在缓存区里。CPU里有转换表缓冲区(TLB
Translation Look-aside Buffers),放置了系统中一个或多个进程的页表条目的缓存的拷贝。</P>
<P> </P>
<P>当引用虚拟地址时,处理区试图在TLB中寻找。如果找到了,它就直接将虚拟地址转换到物理地址,进而对数据执行正确的操作。如果找不到,它就需要操作系统的帮助。它用信号通知操作系统,发生了TLB
missing。一个和系统相关的机制将这个异常转到操作系统相应的代码来处理。操作系统为这个地址映射生成新的TLB条目。当异常清除之后,处理器再次尝试转换虚拟地址,这一次将会成功因为TLB中该地址有了一个有效的条目。</P>
<P> </P>
<P>高速缓存的副作用(不管是硬件或其他方式的)在于Linux必须花大量时间和空间来维护这些高速缓存区,如果这些高速缓存区崩溃,系统也会崩溃。</P>
<P> </P>
<P>3.3 Linux Page Tables(Linux页表)</P>
<P> </P>
<P>Linux假定了三级页表。访问的每一个页表包括了下一级页表的页编号。图3.3显示了一个虚拟地址如何分为一系列字段:每一个字段提供了在一个页表中的偏移量。为了将虚拟地址转换为物理地址,处理器必须取得每一级字段的内容,转换为包括该页表的物理页内的偏移,然后读取下一级页表的页编号。重复三次直到包括虚拟地址的物理地址的页编号找到为止。然后用虚拟地址中的最后一个字段:字节偏移量,在页内查找数据。</P>
<P> </P>
<P>Linux运行的每一个平台都必须提供转换宏,让核心处理特定进程的页表。这样,核心不需要知道页表条目的具体结构或者如何组织。通过这种方式,Linux成功地使用了相同的页表处理程序用于Alpha和Intel
x86处理器,其中Alpha使用三级页表,而Intel使用二级页表。</P>
<P>参见include/asm/pgtable.h</P>
<P> </P>
<P>3.4 Page Allocation and Deallocation (页的分配和回收)</P>
<P> </P>
<P>系统中对于物理页有大量的需求。例如,当程序映像加载到内存中的时候,操作系统需要分配页。当程序结束执行并卸载时需要释放这些页。另外为了存放核心相关的数据结构比如页表自身,也需要物理页。这种用于分配和回收页的机制和数据结构对于维护虚拟内存子系统的效率也许是最重要的。</P>
<P> </P>
<P>系统中的所有的物理页都使用mem_map数据结构来描述。这是一个mem_map_t结构的链表,在启动时进行初始化。每一个mem_map_t(容易混淆的是这个结构也被称为page
结构)结构描述系统中的一个物理页。重要的字段(至少对于内存管理而言)是:</P>
<P>参见include/linux/mm.h</P>
<P> </P>
<P>count 本页用户数目。如果本页由多个进程共享,计数器大于1。</P>
<P>Age 描述本页的年龄。用于决定本页是否可以废弃或交换出去。</P>
<P>Map_nr mem_map_t描述的物理页编号。 </P>
<P>页分配代码使用free_area向量来查找空闲的页。整个缓冲管理方案用这种机制来支持。只要用了这种代码,处理器使用的页的大小和物理页的机制就可以无关。</P>
<P> </P>
<P>每一个free_area单元包括页块的信息。数组中的第一个单元描述了单页,下一个是2页大小的块,下一个是4页大小的块,以此类推,依次向上都是2的倍数。这个链表单元用作队列的开头,有指向mem_map数组中页的数据结构的指针。空闲的页块在这里排队。Map是一个跟踪这么大小的页的分配组的位图。如果页块中的第N块空闲,则位图中的第N位置位。</P>
<P> </P>
<P>图3.4显示了free_area结构。单元0有一个空闲页(页编号0),单元2有2个4页的空闲块,第一个起始于页编号4,第二个起始于页编号56。</P>
<P> </P>
<P>3.4.1 Page Allocation (页分配)</P>
<P>参见mm/page_alloc.c get_free_pages()</P>
<P> </P>
<P>Linux使用Buddy算法有效地分配和回收页块。页分配代码试图分配一个由一个或多个物理页组成的块。页分配使用2的幂数大小的块。这意味着可以分配1页大小,2页大小,4页大小的块,依此类推。只要系统有满足需要的足够的空闲页(nr_free_pages
>
min_free_pages),分配代码就会在free_area中查找满足需要大小的一个页块。Free_area中的每一个单元都有描述自身大小的页块的占用和空闲情况的位图。例如,数组中的第2个单元拥有描述4页大小的块的空闲和占用的分配图。</P>
<P> </P>
<P>这个算法首先找它请求大小的内存页块。它跟踪free_area数据结构中的list单元队列中的空闲页的链表。如果请求大小的页块没有空闲,就找下一个尺寸的块(2倍于请求的大小)。继续这一过程一直到遍历了所有的free_area或者找到了空闲页块。如果找到的页块大于请求的页块,则该块将被分开成为合适大小的块。因为所有的块都是2的幂次的页数组成,所以这个分割的过程比较简单,你只需要将它平分就可以了。空闲的块则放到适当的队列,而分配的页块则返回给调用者。</P>
<P> </P>
<P></P>
<P> </P>
<P>例如在图3.4中,如果请求2页的数据块,第一个4页块(起始于页编号4)将会被分为两个2页块。起始于页号4的第一个2页块将会被返回给调用者,而第二个2页块(起始于页号6)将会排在free_area数组中的单元1中2页空闲块的队列中。</P>
<P> </P>
<P>3.4.2 Page Deallocation(页回收)</P>
<P> </P>
<P>分配页块的过程中将大的页块分为小的页块,将会使内存更为零散。页回收的代码只要可能就把页联成大的页块。其实页块的大小很重要(2的幂数),因为这样才能很容易将页块组成大的页块。</P>
<P> </P>
<P>只要一个页块回收,就检查它的相邻或一起的同样大小的页块是否空闲。如果是这样,就把它和新释放的页块一起组成以一个新的下一个大小的空闲页块。每一次两个内存页块组合成为更大的页块时,页回收代码都要试图将页块合并成为更大的块。这样,空闲的页块就会尽可能的大。</P>
<P>例如,在图3.4,如果页号1释放,那么它会和已经空闲的页号0一起组合并放在free_area的单元1中空闲的2页块队列中。</P>
<P> </P>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -