📄 linu家园-linux kernel核心中文手册.htm
字号:
<P> </P>
<P>Hash table可用于加速常用的数据结构的访问,在Linux里常用hash
table来实现缓冲。缓冲是需要快速存取的信息,是全部可用信息的一个子集。数据结构被放在缓冲区并保留在那里,因为核心经常访问这些结构。使用缓冲区也有副作用,因为使用起来比简单链表或者散列表更加复杂。如果数据结构可以在缓冲区找到(这叫做缓冲命中),那么一切很完美。但是如果数据结构不在缓冲区中,那么必须查找所用的相关的数据结构,如果找到,那么就加到缓冲区中。增加新的数据结构到缓冲区中可能需要废弃一个旧的缓冲入口。Linux必须决定废弃那一个数据结构,风险在于废弃的可能使Linux下一个要访问的数据结构。</P>
<P> </P>
<P>2.3.3 Abstract Interfaces(抽象接口)</P>
<P> </P>
<P>Linux核心经常将它的接口抽象化。接口是以特定方式工作的一系列例程和数据结构。比如:所有的网络设备驱动程序都必须提供特定的例程来处理特定的数据结构。用抽象接口的方式可以用通用的代码层来使用底层特殊代码提供的服务(接口)。例如网络层是通用的,而它由底层符合标准接口的同设备相关的代码提供支持。</P>
<P>通常这些底层在启动时向高一层登记。这个登记过程常通过在链接表中增加一个数据结构来实现。例如,每一个连结到核心的文件系统在核心启动时进行登记(或者如果你使用模块,在文件系统第一次使用时向核心登记)。你可以查看文件/proc/filesystems来检查那些文件系统进行了登记。登记所用的数据结构通常包括指向函数的指针。这是执行特定任务的软件函数的地址。再一次用文件系统登记的例子,每一个文件系统登记时传递给Linux核心的数据结构都包括一个和具体文件系统相关的例程地址,在安装文件系统时必须调用。</P>
<P> </P>
<P> </P>
<P>Chapter 3 </P>
<P>Memory Management (内存管理)</P>
<P> </P>
<P>内存管理子系统是操作系统的重要部分。从计算机发展早期开始,就存在对于大于系统中物理能力的内存需要。为了克服这种限制,开发了许多种策略,其中最成功的就是虚拟内存。虚拟内存通过在竞争进程之间共享内存的方式使系统显得拥有比实际更多的内存。</P>
<P>虚拟内存不仅仅让你的计算机内存显得更多,内存管理子系统还提供:</P>
<P> </P>
<P>Large Address Spaces(巨大的地址空间)操作系统使系统显得拥有比实际更大量的内存。虚拟内存可以比系统中的物理内存大许多倍。</P>
<P>Protection(保护)系统中的每一个进程都有自己的虚拟地址空间。这些虚拟的地址空间是相互完全分离的,所以运行一个应用程序的进程不会影响另外的进程。另外,硬件的虚拟内存机制允许对内存区写保护。这可以防止代码和数据被恶意的程序覆盖。</P>
<P>Memory Mapping(内存映射)内存映射用来将映像和数据映射到进程的地址空间。用内存映射,文件的内容被直接连结到进程的虚拟地址空间。</P>
<P>Fair Physics Memory Allocation(公平分配物理内存)内存管理子系统允许系统中每一个运行中的进程公平地共享系统的物理内存</P>
<P>Shared Virtual
Memory(共享虚拟内存)虽然虚拟内存允许进程拥有分离(虚拟)的地址空间,有时你也需要进程之间共享内存。例如,系统中可能有多个进程运行命令解释程序bash。虽然可以在每一个进程的虚拟地址空间都拥有一份bash的拷贝,更好的是在物理内存中只拥有一份拷贝,所有运行bash的进程共享代码。动态连接库是多个进程共享执行代码的另一个常见例子。共享内存也可以用于进程间通讯(IPC)机制,两个或多个进程可以通过共同拥有的内存交换信息。Linux系统支持系统V的共享内存IPC机制。</P>
<P> </P>
<P>3.1 An Abstract Model of Virtual Memory(虚拟内存的抽象模型)</P>
<P> </P>
<P>在考虑Linux支持虚拟内存的方法之前,最好先考虑一个抽象的模型,以免被太多的细节搞乱。</P>
<P> </P>
<P>在进程执行程序的时候,它从内存中读取指令并进行解码。解码指令也许需要读取或者存储内存特定位置的内容,然后进程执行指令并转移到程序中的下一条指令。进程不管是读取指令还是存取数据都要访问内存。
</P>
<P>在一个虚拟内存系统中,所有的地址都是虚拟地址而非物理地址。处理器通过操作系统保存的一组信息将虚拟地址转换为物理地址。</P>
<P> </P>
<P>为了让这种转换更简单,将虚拟内存和物理内存分为适当大小的块,叫做页(page)。页的大小一样。(当然可以不一样,但是这样一来系统管理起来比较困难)。Linux在Alpha
AXP系统上使用8K字节的页,而在Intel x86系统上使用4K字节的页。每一页都赋予一个唯一编号:page frame number(PFN
页编号)。在这种分页模型下,虚拟地址由两部分组成:虚拟页号和页内偏移量。假如页大小是4K,则虚拟地址的位11到0包括页内偏移量,位12和以上的位是页编号。每一次处理器遇到虚拟地址,它必须提取出偏移和虚拟页编号。处理器必须将虚拟页编号转换到物理的页,并访问物理页的正确偏移处。为此,处理器使用了页表(page
tables)。</P>
<P>图3.1显示了两个进程的虚拟地址空间,进程X和进程Y,每一个进程拥有自己的页表。这些页表将每一个进程的虚拟页映射到内存的物理页上。图中显示进程X的虚拟页号0映射到物理页号1,而进程Y的虚拟页编号1映射到物理页号4。理论上页表每一个条目包括以下信息:</P>
<P> </P>
<P>有效标志 表示页表本条目是否有效</P>
<P>本页表条目描述的物理页编号 访问控制信息 描述本页如何使用:是否可以写?是否包括执行代码?</P>
<P> </P>
<P>页表通过虚拟页标号作为偏移来访问。虚拟页编号5是表中的第6个元素(0是第一个元素)</P>
<P>要将虚拟地址转换到物理地址,处理器首先找出虚拟地址的页编号和页内偏移量。使用2的幂次的页尺寸,可以用掩码或移位简单地处理。再一次看图3.1,假设页大小是0x2000(十进制8192),进程Y的虚拟地址空间的地址是0x2194,处理器将会把地址转换为虚拟页编号1内的偏移量0x194。</P>
<P> </P>
<P></P>
<P> </P>
<P>处理器使用虚拟页编号作为索引在进程的页表中找到它的页表的条目。如果该条目有效,处理器从该条目取出物理的页编号。如果本条目无效,就是进程访问了它的虚拟内存中不存在的区域。在这种情况下,处理器无法解释地址,必须将控制权传递给操作系统来处理。</P>
<P>处理器具体如何通知操作系统进程在访问无法转换的无效的虚拟地址,这个方式是和处理器相关的。处理器将这种信息(page
fault)进行传递,操作系统得到通知,虚拟地址出错,以及出错的原因。</P>
<P> </P>
<P>假设这是一个有效的页表条目,处理器取出物理页号并乘以页大小,得到了物理内存中本页的基础地址。最后,处理器加上它需要的指令或数据的偏移量。</P>
<P> </P>
<P>再用上述例子,进程Y的虚拟页编号1映射到了物理页编号4(起始于0x8000 , 4x
0x2000),加上偏移0x194,得到了最终的物理地址0x8194。</P>
<P> </P>
<P>通过这种方式将虚拟地址映射到物理地址,虚拟内存可以用任意顺序映射到系统的物理内存中。例如,图3.1
中,虚拟内存X的虚拟页编号映射到了物理页编号1而虚拟页编号7虽然在虚拟内存中比虚拟页0要高,却映射到了物理页编号0。这也演示了虚拟内存的一个有趣的副产品:虚拟内存页不必按指定顺序映射到物理内存中。</P>
<P> </P>
<P>3.1.1 Demand Paging</P>
<P> </P>
<P>因为物理内存比虚拟内存少得多,操作系统必须避免无效率地使用物理内存。节省物理内存的一种方法是只加载执行程序正在使用的虚拟页。例如:一个数据库程序可能正在数据库上运行一个查询。在这种情况下,并非所有的数据必须放到内存中,而只需要正被检查的数据记录。如果这是个查找型的查询,那么加载程序中增加记录的代码就没什么意义。这种进行访问时才加载虚拟页的技术叫做demand
paging。</P>
<P> </P>
<P>当一个进程试图访问当前不在内存中的虚拟地址的时候处理器无法找到引用的虚拟页对应的页表条目。例如:图3.1中进程X的页表中没有虚拟页2
的条目,所以如果进程X试图从虚拟页2中的地址读取时,处理器无法将地址转换为物理地址。这时处理器通知操作系统发生page fault。</P>
<P> </P>
<P>如果出错的虚拟地址无效意味着进程试图访问它不应该访问的虚拟地址。也许是程序出错,例如向内存中任意地址写。这种情况下,操作系统会中断它,从而保护系统中其他的进程。</P>
<P>如果出错的虚拟地址有效但是它所在的页当前不在内存中,操作系统必须从磁盘映像中将相应的页加载到内存中。相对来讲磁盘存取需要较长时间,所以进程必须等待直到该页被取到内存中。如果当前有其他系统可以运行,操作系统将选择其中一个运行。取到的页被写到一个空闲的页面,并将一个有效的虚拟页条目加到进程的页表中。然后这个进程重新运行发生内存错误的地方的机器指令。这一次虚拟内存存取进行时,处理器能够将虚拟地址转换到物理地址,所以进程得以继续运行。</P>
<P> </P>
<P>Linux使用demand
paging技术将可执行映像加载到进程的虚拟内存中。当一个命令执行时,包含它的文件被打开,它的内容被映射到进程的虚拟内存中。这个过程是通过修改描述进程内存映射的数据结构来实现,也叫做内存映射(memory
mapping)。但是,实际上只有映像的第一部分真正放在了物理内存中。映像的其余部分仍旧在磁盘上。当映像执行时,它产生page
fault,Linux使用进程的内存映像表来确定映像的那一部分需要加载到内存中执行。 </P>
<P>3.1.2 Swapping(交换)</P>
<P> </P>
<P>如果进程需要将虚拟页放到物理内存中而此时已经没有空闲的物理页,操作系统必须废弃物理空间中的另一页,为该页让出空间。</P>
<P> </P>
<P>如果物理内存中需要废弃的页来自磁盘上的映像或者数据文件,而且没有被写过所以不需要存储,则该页被废弃。如果进程又需要该页,它可以从映像或数据文件中再次加载到内存中。</P>
<P>但是,如果该页已经被改变,操作系统必须保留它的内容以便以后进行访问。这种也叫做dirty
page,当它从物理内存中废弃时,被存到一种叫做交换文件的特殊文件中。因为访问交换文件的速度和访问处理器以及物理内存的速度相比很慢,操作系统必须判断是将数据页写到磁盘上还是将它们保留在内存中以便下次访问。
</P>
<P>如果决定哪些页需要废弃或者交换的算法效率不高,则会发生颠簸(thrashing)。这时,页不断地被写到磁盘上,又被读回,操作系统过于繁忙而无法执行实际的工作。例如在图3.1中,如果物理页号1经常被访问,那么就不要将它交换到硬盘上。进程正在使用的也叫做工作集(working
set)。有效的交换方案应该保证所有进程的工作集都在物理内存中。</P>
<P> </P>
<P>Linux使用LRU(Least Recently
Used最近最少使用)的页面技术来公平地选择需要从系统中废弃的页面。这种方案将系统中的每一页都赋予一个年龄,这个年龄在页面存取时改变。页面访问越多,年纪越轻,越少访问,年纪越老越陈旧。陈旧的页面是交换的好候选。</P>
<P> </P>
<P>3.1.3 Shared Vitual Memory(共享虚拟内存)</P>
<P> </P>
<P>虚拟内存使多个进程可以方便地共享内存。所有的内存访问都是通过页表,每一个进程都有自己的页表。对于两个共享一个物理内存页的进程,这个物理页编号必须出现在两个进程的页表中。</P>
<P>图3.1显示了两个共享物理页号4的进程。对于进程X虚拟页号是4,而对于进程Y虚拟页号是6。这也表明了共享页的一个有趣的地方:共享的物理页不必存在共享它的进程的虚拟内存空间的同一个地方。</P>
<P> </P>
<P>3.1.4 Physical and Vitual Addressing Modes(物理和虚拟寻址模式)</P>
<P> </P>
<P>对于操作系统本身而言,运行在虚拟内存中没有什么意义。如果操作系统必须维护自身的页表,这将会是一场噩梦。大多数多用途的处理器同时支持物理地址模式和虚拟地址模式。物理寻址模式不需要页表,处理器在这种模式下不需要进行任何地址转换。Linux核心运行在物理地址模式。</P>
<P> </P>
<P>Alpha
AXP处理器没有特殊的物理寻址模式。它将内存空间分为几个区,将其中两个指定为物理映射地址区。核心的地址空间叫做KSEG地址空间,包括从0xfffffc0000000000向上的所有地址。为了执行连接在KSEG的代码(核心代码)或者访问那里的数据,代码必须在核心态执行。Alpha
上的Linux核心连接到从地址0xfffffc0000310000执行。</P>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -