笔记:深入理解Linux内核(二)

二零二一年十月二十四日

文章目录

  • 笔记:深入理解Linux内核(二)
    • 第二章:内存寻址
      • 内存地址
      • 硬件中的分段
        • 段选择符和段选择器
        • 段描述符
        • 快速访问段描述符
        • 分段单元
      • Linux中的分段
        • Linux GDT
        • Linux LDT
      • 硬件中的分页
        • 常规分页
        • 扩展分页
        • 硬件保护方案
        • 物理地址扩展(PAE)分页机制
        • 64位系统中的分页
        • 硬件高速缓存
        • 转换后援缓冲器(TLB)
      • Linux中的分页
        • 线性地址字段
        • 页表处理
        • 物理内存布局
        • 进程页表
        • 内核页表
          • 临时内核页表
          • 当RAM小于896MB时的最终内核页表
          • 当RAM大小在896MB和4096MB之间时的最终内核页表
          • 当RAM大于4096MB时的最终内核页表
        • 固定映射的线性地址
        • 处理硬件高速缓存和TLB
          • 处理硬件高速缓存
          • 处理TLB

第二章:内存寻址

本章介绍寻址技术,详尽描述80x86微处理器怎样进行芯片级的内存寻址。Linux如何利用寻址硬件。

由题而问:

  1. 内存地址和其他存储器有什么区别?它分区吗,如果分区,根据是什么?
  2. 为什么要在硬件中分段?软件也分段:Linux中的分段。
  3. 分段是什么,那么分页呢?同样硬件软件都要分页。
  4. 分段、分页是必要的吗?

内存地址

内存地址(memory address)是访问内存单元内容的一种方式。

在80x86微处理器中有如下三种不同的地址:

  • 逻辑地址(logical address):包含在机器语言指令中,用来指定一个操作数或一条指令的地址。每个逻辑地址都由一个段(segment)和偏移量(offset或displacement)组成,偏移量指明了从段开始的地方到实际地址之间的距离。

  • 线性地址(linear address 又称虚拟地址virtual address):是一个32位无符号整数,可以用来表示高达4GB的地址。也就是高达4 294 967 296个内存单元。线性地址通常用十六进制数字表示,值的范围从0x00000000到0xffffffff。

    1B(字节)=8b(位) 1 KB = 1024 B 1 MB = 1024 KB 1 GB = 1024 MB 1TB = 1024GB

    这里的32指的是CPU的32位地址线,可以访问2^32个不同的地址。其次,CPU有8位位线,即访问一个地址可以获得8位的数据,即1字节(1B)的数据。

    或者说CPU的一个地址对应一个内存单元,就是1个字节8位。

    所以线性地址的32位对应32字节的数据,就是2的32次方个字节,4GB。

  • 物理地址(physical address):用于内存芯片级内存单元寻址。它们与从微处理器的地址引脚发送到内存总线上的电信号相对应。通常由32位或36位无符号整数表示

内存控制单元(MMU)通过一种被称为分段单元(segmentation unit)的硬件电路把一个逻辑地址转化成线性地址。接着,被称为分页单元(paging unit)的硬件电路把线性地址转换成一个物理地址。

#mermaid-svg-fSnjDniLNSxCzxVh .label{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);fill:#333;color:#333}#mermaid-svg-fSnjDniLNSxCzxVh .label text{fill:#333}#mermaid-svg-fSnjDniLNSxCzxVh .node rect,#mermaid-svg-fSnjDniLNSxCzxVh .node circle,#mermaid-svg-fSnjDniLNSxCzxVh .node ellipse,#mermaid-svg-fSnjDniLNSxCzxVh .node polygon,#mermaid-svg-fSnjDniLNSxCzxVh .node path{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-fSnjDniLNSxCzxVh .node .label{text-align:center;fill:#333}#mermaid-svg-fSnjDniLNSxCzxVh .node.clickable{cursor:pointer}#mermaid-svg-fSnjDniLNSxCzxVh .arrowheadPath{fill:#333}#mermaid-svg-fSnjDniLNSxCzxVh .edgePath .path{stroke:#333;stroke-width:1.5px}#mermaid-svg-fSnjDniLNSxCzxVh .flowchart-link{stroke:#333;fill:none}#mermaid-svg-fSnjDniLNSxCzxVh .edgeLabel{background-color:#e8e8e8;text-align:center}#mermaid-svg-fSnjDniLNSxCzxVh .edgeLabel rect{opacity:0.9}#mermaid-svg-fSnjDniLNSxCzxVh .edgeLabel span{color:#333}#mermaid-svg-fSnjDniLNSxCzxVh .cluster rect{fill:#ffffde;stroke:#aa3;stroke-width:1px}#mermaid-svg-fSnjDniLNSxCzxVh .cluster text{fill:#333}#mermaid-svg-fSnjDniLNSxCzxVh div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:12px;background:#ffffde;border:1px solid #aa3;border-radius:2px;pointer-events:none;z-index:100}#mermaid-svg-fSnjDniLNSxCzxVh .actor{stroke:#ccf;fill:#ECECFF}#mermaid-svg-fSnjDniLNSxCzxVh text.actor>tspan{fill:#000;stroke:none}#mermaid-svg-fSnjDniLNSxCzxVh .actor-line{stroke:grey}#mermaid-svg-fSnjDniLNSxCzxVh .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333}#mermaid-svg-fSnjDniLNSxCzxVh .messageLine1{stroke-width:1.5;stroke-dasharray:2, 2;stroke:#333}#mermaid-svg-fSnjDniLNSxCzxVh #arrowhead path{fill:#333;stroke:#333}#mermaid-svg-fSnjDniLNSxCzxVh .sequenceNumber{fill:#fff}#mermaid-svg-fSnjDniLNSxCzxVh #sequencenumber{fill:#333}#mermaid-svg-fSnjDniLNSxCzxVh #crosshead path{fill:#333;stroke:#333}#mermaid-svg-fSnjDniLNSxCzxVh .messageText{fill:#333;stroke:#333}#mermaid-svg-fSnjDniLNSxCzxVh .labelBox{stroke:#ccf;fill:#ECECFF}#mermaid-svg-fSnjDniLNSxCzxVh .labelText,#mermaid-svg-fSnjDniLNSxCzxVh .labelText>tspan{fill:#000;stroke:none}#mermaid-svg-fSnjDniLNSxCzxVh .loopText,#mermaid-svg-fSnjDniLNSxCzxVh .loopText>tspan{fill:#000;stroke:none}#mermaid-svg-fSnjDniLNSxCzxVh .loopLine{stroke-width:2px;stroke-dasharray:2, 2;stroke:#ccf;fill:#ccf}#mermaid-svg-fSnjDniLNSxCzxVh .note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-fSnjDniLNSxCzxVh .noteText,#mermaid-svg-fSnjDniLNSxCzxVh .noteText>tspan{fill:#000;stroke:none}#mermaid-svg-fSnjDniLNSxCzxVh .activation0{fill:#f4f4f4;stroke:#666}#mermaid-svg-fSnjDniLNSxCzxVh .activation1{fill:#f4f4f4;stroke:#666}#mermaid-svg-fSnjDniLNSxCzxVh .activation2{fill:#f4f4f4;stroke:#666}#mermaid-svg-fSnjDniLNSxCzxVh .mermaid-main-font{font-family:"trebuchet ms", verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-fSnjDniLNSxCzxVh .section{stroke:none;opacity:0.2}#mermaid-svg-fSnjDniLNSxCzxVh .section0{fill:rgba(102,102,255,0.49)}#mermaid-svg-fSnjDniLNSxCzxVh .section2{fill:#fff400}#mermaid-svg-fSnjDniLNSxCzxVh .section1,#mermaid-svg-fSnjDniLNSxCzxVh .section3{fill:#fff;opacity:0.2}#mermaid-svg-fSnjDniLNSxCzxVh .sectionTitle0{fill:#333}#mermaid-svg-fSnjDniLNSxCzxVh .sectionTitle1{fill:#333}#mermaid-svg-fSnjDniLNSxCzxVh .sectionTitle2{fill:#333}#mermaid-svg-fSnjDniLNSxCzxVh .sectionTitle3{fill:#333}#mermaid-svg-fSnjDniLNSxCzxVh .sectionTitle{text-anchor:start;font-size:11px;text-height:14px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-fSnjDniLNSxCzxVh .grid .tick{stroke:#d3d3d3;opacity:0.8;shape-rendering:crispEdges}#mermaid-svg-fSnjDniLNSxCzxVh .grid .tick text{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-fSnjDniLNSxCzxVh .grid path{stroke-width:0}#mermaid-svg-fSnjDniLNSxCzxVh .today{fill:none;stroke:red;stroke-width:2px}#mermaid-svg-fSnjDniLNSxCzxVh .task{stroke-width:2}#mermaid-svg-fSnjDniLNSxCzxVh .taskText{text-anchor:middle;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-fSnjDniLNSxCzxVh .taskText:not([font-size]){font-size:11px}#mermaid-svg-fSnjDniLNSxCzxVh .taskTextOutsideRight{fill:#000;text-anchor:start;font-size:11px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-fSnjDniLNSxCzxVh .taskTextOutsideLeft{fill:#000;text-anchor:end;font-size:11px}#mermaid-svg-fSnjDniLNSxCzxVh .task.clickable{cursor:pointer}#mermaid-svg-fSnjDniLNSxCzxVh .taskText.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-fSnjDniLNSxCzxVh .taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-fSnjDniLNSxCzxVh .taskTextOutsideRight.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-fSnjDniLNSxCzxVh .taskText0,#mermaid-svg-fSnjDniLNSxCzxVh .taskText1,#mermaid-svg-fSnjDniLNSxCzxVh .taskText2,#mermaid-svg-fSnjDniLNSxCzxVh .taskText3{fill:#fff}#mermaid-svg-fSnjDniLNSxCzxVh .task0,#mermaid-svg-fSnjDniLNSxCzxVh .task1,#mermaid-svg-fSnjDniLNSxCzxVh .task2,#mermaid-svg-fSnjDniLNSxCzxVh .task3{fill:#8a90dd;stroke:#534fbc}#mermaid-svg-fSnjDniLNSxCzxVh .taskTextOutside0,#mermaid-svg-fSnjDniLNSxCzxVh .taskTextOutside2{fill:#000}#mermaid-svg-fSnjDniLNSxCzxVh .taskTextOutside1,#mermaid-svg-fSnjDniLNSxCzxVh .taskTextOutside3{fill:#000}#mermaid-svg-fSnjDniLNSxCzxVh .active0,#mermaid-svg-fSnjDniLNSxCzxVh .active1,#mermaid-svg-fSnjDniLNSxCzxVh .active2,#mermaid-svg-fSnjDniLNSxCzxVh .active3{fill:#bfc7ff;stroke:#534fbc}#mermaid-svg-fSnjDniLNSxCzxVh .activeText0,#mermaid-svg-fSnjDniLNSxCzxVh .activeText1,#mermaid-svg-fSnjDniLNSxCzxVh .activeText2,#mermaid-svg-fSnjDniLNSxCzxVh .activeText3{fill:#000 !important}#mermaid-svg-fSnjDniLNSxCzxVh .done0,#mermaid-svg-fSnjDniLNSxCzxVh .done1,#mermaid-svg-fSnjDniLNSxCzxVh .done2,#mermaid-svg-fSnjDniLNSxCzxVh .done3{stroke:grey;fill:#d3d3d3;stroke-width:2}#mermaid-svg-fSnjDniLNSxCzxVh .doneText0,#mermaid-svg-fSnjDniLNSxCzxVh .doneText1,#mermaid-svg-fSnjDniLNSxCzxVh .doneText2,#mermaid-svg-fSnjDniLNSxCzxVh .doneText3{fill:#000 !important}#mermaid-svg-fSnjDniLNSxCzxVh .crit0,#mermaid-svg-fSnjDniLNSxCzxVh .crit1,#mermaid-svg-fSnjDniLNSxCzxVh .crit2,#mermaid-svg-fSnjDniLNSxCzxVh .crit3{stroke:#f88;fill:red;stroke-width:2}#mermaid-svg-fSnjDniLNSxCzxVh .activeCrit0,#mermaid-svg-fSnjDniLNSxCzxVh .activeCrit1,#mermaid-svg-fSnjDniLNSxCzxVh .activeCrit2,#mermaid-svg-fSnjDniLNSxCzxVh .activeCrit3{stroke:#f88;fill:#bfc7ff;stroke-width:2}#mermaid-svg-fSnjDniLNSxCzxVh .doneCrit0,#mermaid-svg-fSnjDniLNSxCzxVh .doneCrit1,#mermaid-svg-fSnjDniLNSxCzxVh .doneCrit2,#mermaid-svg-fSnjDniLNSxCzxVh .doneCrit3{stroke:#f88;fill:#d3d3d3;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}#mermaid-svg-fSnjDniLNSxCzxVh .milestone{transform:rotate(45deg) scale(0.8, 0.8)}#mermaid-svg-fSnjDniLNSxCzxVh .milestoneText{font-style:italic}#mermaid-svg-fSnjDniLNSxCzxVh .doneCritText0,#mermaid-svg-fSnjDniLNSxCzxVh .doneCritText1,#mermaid-svg-fSnjDniLNSxCzxVh .doneCritText2,#mermaid-svg-fSnjDniLNSxCzxVh .doneCritText3{fill:#000 !important}#mermaid-svg-fSnjDniLNSxCzxVh .activeCritText0,#mermaid-svg-fSnjDniLNSxCzxVh .activeCritText1,#mermaid-svg-fSnjDniLNSxCzxVh .activeCritText2,#mermaid-svg-fSnjDniLNSxCzxVh .activeCritText3{fill:#000 !important}#mermaid-svg-fSnjDniLNSxCzxVh .titleText{text-anchor:middle;font-size:18px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-fSnjDniLNSxCzxVh g.classGroup text{fill:#9370db;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:10px}#mermaid-svg-fSnjDniLNSxCzxVh g.classGroup text .title{font-weight:bolder}#mermaid-svg-fSnjDniLNSxCzxVh g.clickable{cursor:pointer}#mermaid-svg-fSnjDniLNSxCzxVh g.classGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-fSnjDniLNSxCzxVh g.classGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-fSnjDniLNSxCzxVh .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5}#mermaid-svg-fSnjDniLNSxCzxVh .classLabel .label{fill:#9370db;font-size:10px}#mermaid-svg-fSnjDniLNSxCzxVh .relation{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-fSnjDniLNSxCzxVh .dashed-line{stroke-dasharray:3}#mermaid-svg-fSnjDniLNSxCzxVh #compositionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-fSnjDniLNSxCzxVh #compositionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-fSnjDniLNSxCzxVh #aggregationStart{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-fSnjDniLNSxCzxVh #aggregationEnd{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-fSnjDniLNSxCzxVh #dependencyStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-fSnjDniLNSxCzxVh #dependencyEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-fSnjDniLNSxCzxVh #extensionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-fSnjDniLNSxCzxVh #extensionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-fSnjDniLNSxCzxVh .commit-id,#mermaid-svg-fSnjDniLNSxCzxVh .commit-msg,#mermaid-svg-fSnjDniLNSxCzxVh .branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-fSnjDniLNSxCzxVh .pieTitleText{text-anchor:middle;font-size:25px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-fSnjDniLNSxCzxVh .slice{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-fSnjDniLNSxCzxVh g.stateGroup text{fill:#9370db;stroke:none;font-size:10px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-fSnjDniLNSxCzxVh g.stateGroup text{fill:#9370db;fill:#333;stroke:none;font-size:10px}#mermaid-svg-fSnjDniLNSxCzxVh g.statediagram-cluster .cluster-label text{fill:#333}#mermaid-svg-fSnjDniLNSxCzxVh g.stateGroup .state-title{font-weight:bolder;fill:#000}#mermaid-svg-fSnjDniLNSxCzxVh g.stateGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-fSnjDniLNSxCzxVh g.stateGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-fSnjDniLNSxCzxVh .transition{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-fSnjDniLNSxCzxVh .stateGroup .composit{fill:white;border-bottom:1px}#mermaid-svg-fSnjDniLNSxCzxVh .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px}#mermaid-svg-fSnjDniLNSxCzxVh .state-note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-fSnjDniLNSxCzxVh .state-note text{fill:black;stroke:none;font-size:10px}#mermaid-svg-fSnjDniLNSxCzxVh .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.7}#mermaid-svg-fSnjDniLNSxCzxVh .edgeLabel text{fill:#333}#mermaid-svg-fSnjDniLNSxCzxVh .stateLabel text{fill:#000;font-size:10px;font-weight:bold;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-fSnjDniLNSxCzxVh .node circle.state-start{fill:black;stroke:black}#mermaid-svg-fSnjDniLNSxCzxVh .node circle.state-end{fill:black;stroke:white;stroke-width:1.5}#mermaid-svg-fSnjDniLNSxCzxVh #statediagram-barbEnd{fill:#9370db}#mermaid-svg-fSnjDniLNSxCzxVh .statediagram-cluster rect{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-fSnjDniLNSxCzxVh .statediagram-cluster rect.outer{rx:5px;ry:5px}#mermaid-svg-fSnjDniLNSxCzxVh .statediagram-state .divider{stroke:#9370db}#mermaid-svg-fSnjDniLNSxCzxVh .statediagram-state .title-state{rx:5px;ry:5px}#mermaid-svg-fSnjDniLNSxCzxVh .statediagram-cluster.statediagram-cluster .inner{fill:white}#mermaid-svg-fSnjDniLNSxCzxVh .statediagram-cluster.statediagram-cluster-alt .inner{fill:#e0e0e0}#mermaid-svg-fSnjDniLNSxCzxVh .statediagram-cluster .inner{rx:0;ry:0}#mermaid-svg-fSnjDniLNSxCzxVh .statediagram-state rect.basic{rx:5px;ry:5px}#mermaid-svg-fSnjDniLNSxCzxVh .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#efefef}#mermaid-svg-fSnjDniLNSxCzxVh .note-edge{stroke-dasharray:5}#mermaid-svg-fSnjDniLNSxCzxVh .statediagram-note rect{fill:#fff5ad;stroke:#aa3;stroke-width:1px;rx:0;ry:0}:root{--mermaid-font-family: '"trebuchet ms", verdana, arial';--mermaid-font-family: "Comic Sans MS", "Comic Sans", cursive}#mermaid-svg-fSnjDniLNSxCzxVh .error-icon{fill:#522}#mermaid-svg-fSnjDniLNSxCzxVh .error-text{fill:#522;stroke:#522}#mermaid-svg-fSnjDniLNSxCzxVh .edge-thickness-normal{stroke-width:2px}#mermaid-svg-fSnjDniLNSxCzxVh .edge-thickness-thick{stroke-width:3.5px}#mermaid-svg-fSnjDniLNSxCzxVh .edge-pattern-solid{stroke-dasharray:0}#mermaid-svg-fSnjDniLNSxCzxVh .edge-pattern-dashed{stroke-dasharray:3}#mermaid-svg-fSnjDniLNSxCzxVh .edge-pattern-dotted{stroke-dasharray:2}#mermaid-svg-fSnjDniLNSxCzxVh .marker{fill:#333}#mermaid-svg-fSnjDniLNSxCzxVh .marker.cross{stroke:#333}:root { --mermaid-font-family: "trebuchet ms", verdana, arial;}#mermaid-svg-fSnjDniLNSxCzxVh {color: rgba(0, 0, 0, 0.75);font: ;}

分段单元
分页单元
逻辑地址
线性地址
物理地址

在多处理器系统中,所有CPU都共享同意内存,这意味着RAM芯片可以由独立的CPU并发访问。又因为RAM芯片的读和写操作必须串行执行,所以在总线和RAM之间的硬件电路上有一个所谓的内存仲裁器(memory arbiter)。作用是判断RAM是否空闲,来允许CPU访问。

硬件中的分段

从80286模型开始,Intel微处理器以两种不同的方式执行地址转换,分别是:实模式(real mode)和保护模式(protected mode)。

实模式存在的只要原因是要维持处理器与早期模型兼容,并让操作系统自举。

CPU复位(reset)或加电(power on)的时候以实模式启动,处理器以实模式工作。在实模式下,内存寻址方式和8086相同,由16位段寄存器的内容乘以16(10H)当做段基地址,加上16位偏移地址形成20位的物理地址,最大寻址空间1MB,最大分段64KB。可以使用32位指令。32位的x86 CPU用做高速的8086。在实模式下,所有的段都是可以读、写和可执行的。

286架构导入保护模式,允许硬件等级的存储器保护。然而要使用这些新的特色,需要额外先前不需要的软件指令。由于x86微处理机主要的设计规格,是能够完全地向前兼容于针对先前所有x86芯片所撰写的软件,因此286芯片的开机是处于’实模式’—也就是关闭新的存储器保护特性的模式,所以可以运行针对旧的微处理器所设计的软件。到现在为止,即使最新的x86 CPU一开始在电源打开处于实模式下,也能够运行针对先前任何芯片所撰写的软件.

段选择符和段选择器

从前面的描述中,一个逻辑地址由一个段(segment)和偏移量(offset或displacement)组成。

实际上段是段标识符又称段选择符(Segment Selector),是个16位长的字段。偏移量是32位长的字段。

段标识符:位[0-1]:RPL,请求者特权级
位[2-3]:TI,表指示器
位[4-15]:索引号

为了快速方便地找到段选择符,处理器提供段寄存器,段寄存器的唯一目的就是存放段选择符。这些段寄存器是cs,ss,ds,es,fs和gs。尽管只有六个,但程序可以把同一个段寄存器用于不同的目的,方法是先将其值保存在内存中,用完后再恢复。有三个专门用途:

  • cs:代码段寄存器,指向包含程序指令的段

    cs还有一个重要的功能,它含有两位的字段,用以指明CPU的当前特权级(Current Privilege Level,CPL),0就是最高优先级,3(b11)为最低优先级,Linux只用0和3级,分指内核态和用户态。

  • ss:栈段寄存器,指向包含当前程序栈的段

  • ds:数据段寄存器,指向包含静态数据或者全局数据段

其他三个作一般用途,可以指向任意的数据段。

段描述符

每个段由一个八字节的段描述符(segment descriptor)表示,它描述了段特征,段描述符放在全局描述表(global descriptor table,GDT)或局部描述表(local descriptor table,LDT)中。

通常只定义一个GDT,而每个进程除了存放在GDT中的段之外,如果还需要创建附加的段,就可以有自己的LDT。

GDT在主存中的地址和大小存放在gdtr寄存器中,当前被使用的LDT地址和大小放在ldtr寄存器中

Linux中被广泛采用的类型段和对应的描述符:

  1. 代码段描述符
  2. 数据段描述符
  3. 任务状态段描述符task state segment descriptor,TSSD
  4. 局部描述符表描述符LDTD

快速访问段描述符

为了加速逻辑地址到线性地址的转换,80x86处理器提供了一种附加的非编程的寄存器(不能被程序员所设置的寄存器)供六个可编程的段寄存器使用。每一个非编程的寄存器含有8个字节的段描述符,由相应的段寄存器中的段选择符来指定。

每当一个段选择符被装入段寄存器时,相应的段描述符就由内存装入到对应的非编程寄存器。从这时起,针对那个段的逻辑地址转换就可以不访问主存中的GDT或LDT,处理器只需直接引用存放段描述符的CPU非编程寄存器即可。当且仅当,段寄存器中的段选择符改变时,才有必要访问GDT或LDT。

分段单元

逻辑地址如何转化为线性地址,分段单元(segmentation)会进行如下操作:

  • 先检查段选择符的TI字段,以决定段描述符保存在哪一个描述符表中。
  • 从段选择符的index字段计算段描述符的地址,index字段(索引号)值×8,结果与gdtrhuoldtr寄存器保存的线性基地址相加。
  • 把逻辑地址的偏移量与段描述符base字段的值相加就得到了线性地址。

Linux中的分段

80x86微处理器中的分段鼓励程序员把他们的程序划分成逻辑上相关的实体,例如子程序或者全局与局部数据区。然而Linux以非常有限的方式使用分段。

实际上分段和分页在某种程度上有点多余。因为它们都可以划分进程的物理空间地址。

分段可以给每个进程分配不同的线性地址空间。而分页可以把同一线性地址空间映射到不同的物理地址空间中。与分段相比,Linux更喜欢用分页方式。因为:

  • 当所有的进程使用相同的段寄存器时,内存管理变得更简单,也就是说它们能共享同样的一组线性地址。
  • Linux设计的目的之一就是可以把它移植到绝大多数流行的处理器平台上。然而,RISC体系结构对分段的支持很有限。

运行在用户态下的所有Linux进程都使用一对相同的段来对指令和数据寻址。这两个段就是所谓的用户代码段和用户数据段。类似地,运行在内核态的所有Linux进程都是用另一对相同的段对指令和数据寻址,分别是内核代码段和内核数据段。

注意,与段相关的线性地址从0开始,达到2^32-1的寻址长度,这就意味着用户态或内核态下的所有进程可以使用相同的逻辑地址。

所有的段都是从0x00000000开始,这可以得到另一个结论,那就是Linux下的逻辑地址和线性地址是一致的,即逻辑地址的偏移量字段的值与相应的线性地址的值总是一致的。

段选择符的RPL字段指定了CPU当前特权级CPL,反映了进程是在用户态还是内核态。只要当前特权级被改变,所以段选择符就要改变,保存段选择符的段寄存器也要更新。

当对指向指令或者数据结构的指针保存时,内核根本不用为其设置逻辑地址的段选择符,因为cs寄存器就含有当前的段选择符。

Linux GDT

单处理器系统中只有一个GDT,而在多处理器系统中每一个CPU对应一个GDT。且每个处理器都有一个GDT副本,除少数情况外,所有GDT副本都存放相同的表项。

在Linux2.6中,所有的GDT都存放在cpu_gdt_table数组中而所有的GDT的地址和它们的大小(当初始化gdtr寄存器时使用)被存放在cpu_gdt_descr数组中。

每一个GDT包含18个段描述符和14个空的,未被使用的,或者保留项。插入未使用的项的目的是为了使经常一起访问的描述符能够处于同一个32字节的硬件高速缓存行中。

GDT中十八个段描述符指向下列段:

  1. 用户态和内核态下的代码段和数据段共4个

  2. 任务状态段(TSS),每个处理器有一个。

    用户状态下不允许访问TSS段。

  3. 一个包括默认局部描述符表的段。这个段通常被所有进程共享。

  4. 3个局部线程存储段(Thread-Local Storage,TLS):这种机制允许多线程应用程序使用最多三个局部于线程的数据段。

  5. 与高级电源管理(APM)相关的3个段。

  6. 与支持即插即用(PnP)功能的BIOS服务程序相关的5个段

  7. 被内核用来处理“双重错误”异常的特殊TSS段

    处理一个异常时可能会引发另一个异常,在这种情况下产生双重错误。

Linux LDT

大多数用户态下的Linux程序不使用局部描述符表,这样内核就定义了一个默认的LDT供大多数进程使用。默认的局部描述符表存放在default_ldt数组中。它包含5个项,但内核仅仅有效地使用了其中的两个项:用于iBCS执行文件的调用门和Solaris/x86可执行文件调用门。

调用门是80x86微处理器提供的一种机制,用于在调用预定义函数时改变CPU的特权级。可移步查看Intel文档。

在某些情况下,进程仍需要创建自己的局部描述符表。modify_ldt()系统调用允许进程自己创建自己的局部描述符表。

任何被modify_ldt()创建的自定义局部描述符表仍然需要它自己的段。当处理器开始执行拥有自定义局部描述符表的进程时,该CPU的GDT副本中的LDT表项相应地就被修改。

用户态下的程序同样也利用modify_ldt()来分配新的段。但内核却从不使用这些段,它也不需要了解相应的段描述符,因为这些段描述符被包含在进程自定义的局部描述符表中。

硬件中的分页

分页单元(paging unit)把线性地址转换成物理地址。其中一个关键性任务是把所请求的访问类型与线性地址的访问权限相比较,如果这次内存访问是无效的,就产生一个缺页异常。

为了效率,线性地址被分成以固定长度为单位的组,称为页(page)。页内部连续的线性地址被映射到连续的物理地址中,这样,内核可以指定一个页的物理地址和其存取权限,而不用指定页所包含的全部线性地址的存取权限。

通常习惯“页”既指一组线性地址,又指包含在这组地址中的数据。

分页单元把所有的RAM分成固定长度的页框(page frame)(有时叫做物理页)。每一个页框包含一个页(page),也就是说每一个页框的长度与一个页的长度一致。页框是一个主存的一部分,因此也是一个存储区域。区分一页和一个页框是很重要的,前者只是一个数据块,可以存放在任何页框或磁盘中。

把线性地址映射到物理地址的数据结构称为页表(page table)。页表存放在主存中,并在启用分页单元之前必须由内核对页表进行适当的初始化。

从80386开始,所有的80x86处理器都支持分页,它通过设置cr0寄存器的PG标志启用,当PG=0时,线性地址就被解释成物理地址。

常规分页

从80386开始,Intel处理器的分页单元处理4KB的页

32位线性地址被分为三个域:最高十位是Directory(目录),中间十位是Table(页表),最低十二位是Offset(偏移量)。

线性地址的转换分两步完成,每一步都基于一种转换表,第一种转换表称为页目录表(page directory),第二种转换表称为页表(page table)。

使用这种二级模式是为了减少每一个进程页表所需的RAM的数量。

每个活动进程必须有一个分配给它的页目录。但是没有必要马上为进程所有的页表都分配RAM。只有在进程实际需要一个页表时才给该页表分配RAM会更有效率。

正在使用的页目录的物理地址存放在控制寄存器cr3中。线性地址内的Directory字段决定页目录中的目录项,而目录项指向适当的页表。地址的Table字段依次决定页表中的表项,而表项含有所在页框的物理地址。Offset字段决定也框内的相对位置。如下图。

Directory和Table字段都是10位长,因此页目录和页表都有可以多达1024项。12位的Offset字段相当于每一页含有4096字节的数据。那么,一个页目录可以寻址到高达1024×1024×4096=2^32个存储单元。

注意这里的位同样是位地址线,每页2^12字节的数据就是4KB的数据

页目录项和页表项有相同的结构,每项都含有如下字段:Present字段、Field包含页框物理地址最高20位的字段、Accessed标志等。

扩展分页

从Pentium模型开始,80x86微处理器引入了扩展分页(extended paging),它允许页框大小位4MB而不是4KB。扩展分页用于把大段连续的线性地址转化成相应的物理地址,在这些情况下,内核可以不用在中间页表进行地址转换,从而节省内存并保留TLB项。(转换后援缓冲器)。

在这种情况下,32位线性地址中的Table和Offset字段融合成为22位的Offset字段。

设置cr4处理器寄存器的PSE标志能使扩展分页和常规分页

硬件保护方案

分页单元与分段单元的保护方案不同。尽管80x86处理器允许一个段使用四种可能的权限级别,但页与页表相关的特权级只有两个,因为特权由页目录项和页表项的每项的User/Supervisor标志字段控制,若这个标志为0,则只有当CPL(Current Privilege Level,当前进程权限级别)小于3时(这意味着对Linux而言,进程处于内核态)才能对页寻址;若该标志为1,则总能对页寻址。

此外,每段的3种存取权限(读、写、执行)不同的是,页的存取权限只有两种(读、写)。如果页目录项或页表项的Read/Write标志为0,说明相应的页表或页是只读的,否则是可读写的。

物理地址扩展(PAE)分页机制

处理器所支持的RAM容量受连接到地址总线上的地址管脚数限制。早期Intel处理器从80386到Pentium使用32位物理地址,理论上,这样的系统上可以安装高达4GB的RAM,但是由于用户进程线性地址空间的需要,内核不能直接对1GB以上的RAM直接寻址。

从Pentium Pro开始,Intel引入了一种叫做物理地址拓展(Physical Address Extension,PAE)的机制。

64位系统中的分页

32位微处理器普遍采用两级分页,然而两级分页并不适用采用64位系统的计算机。

64位两级分页的话,页目录项、页表项、表项可能会数量巨大

所有的64位处理器的硬件分页系统都使用了额外的分页级别。使用的级别数量取决于处理器的类型。下表总结了一些Linux所支持的64位平台使用的硬件分页系统的主要特征。

平台名称 页大小 寻址使用的位数 分页级别数 线性地址分级
alpha 8KB^a 43 3 10+10+10+13
ia64 4KB^a 39 3 9+9+9+12
ppc64 4KB 41 3 10+10+9+12
sh64 4KB 41 3 10+10+9+12
x86_64 4KB 48 4 9+9+9+9+12

且,Linux成功地提供了一种通用分页模型,它适合于绝大多数所支持的硬件分页级别。

硬件高速缓存

微处理器的时钟频率非常高,而DRAM(动态RAM)芯片的存取时间是时钟周期的数百倍,为了缩小CPU与RAM之间的速度不匹配,引入了硬件高速缓存内存(hardware cache memory)。

硬件高速缓存基于著名的局部性原理(locality principle),该原理既适用于程序结构也适用于数据结构。这表明由于程序的循环结构及相关数组可以组织成线性数组,最近最常用的相邻地址在最近的将来被用到的可能性极大。因此,引入小而快的内存来存放最近最常使用的代码和数据变得很有意义。

局部性原理是指CPU访问存储器时,无论是存取指令还是存取数据,所访问的存储单元都趋于聚集在一个较小的连续区域中。

为此,80x86体系结构中引入了一个叫行(line)的新单位。行由十几个连续的字节组成,它们以脉冲突发模式(brust mode)在慢速DRAM和快速的用来实现高速缓存的片上静态RAM(SRAM)之间传送,用来实现高速缓存。

高速缓存再被细分为行的子集。

  • 在一种极端情况下,高速缓存是可以直接映射的(direct mapped),这时主存中的一个行总是存放在高速缓存中完全相同的位置;
  • 在另一种极端情况下,高速缓存是充分关联的(fully associative),这意味着主存中的任意一个行可以存放在高速缓存中的任意位置。
  • 但是大多数高速缓存在某种程度上是N-路组关联的(N-way set associative),意味着主存中的任意一个行可以存放在高速缓存N行中任意一行。

高速缓存单元插在分页单元和主内存之间。它包含一个硬件高速缓存内存(hardware cache memory)和一个高速缓存控制器(cache controller)。高速缓存内存存放内存中真正的行。高速缓冲控制器存放一个表项数组,每一个表项对应高速缓存内存中的一个行。每一个表项有一个标签(rag)和描述高速缓存行状态的几个标志(flag)。

这些标签由一些位组成,这些位让高速缓存控制其能够辨别由这个行当前所映射的内存单元。这种内存物理地址通常分为三组:最高几位对应标签,中间几位对应高速缓存控制器的子集索引,最低几位对应行内的偏移量。

当访问一个RAM存储单元时,CPU从物理地址中提取出子集的索引号,并把子集中所有行的标签与物理地址的高几位相比较。如果发现某一行的标签与这个物理地址高位相同,则CPU命中一个高速缓存(cache hit);否则,高速缓存没有命中(cache miss)。

当cache hit时,高速缓存控制器进行不同的操作,具体取决于存取类型。

  • 读操作:控制器从高速缓存行中选择数据并送到CPU寄存器;不需要访问RAM因而节约了CPU时间,因此,高速缓存系统起到了其应有的作用。

  • 写操作:控制器可能采用以下两种基本策略之一。

    通写(write-through),控制器总是既写RAM也写高速缓存行,为了提高写操作的效率关闭高速缓存。

    回写(write-back),只更新高速缓存行,不改变RAM的内容,提供了更快的功效。当然回写结束后,RAM最终必须被更新。

只有当CPU执行一条要求刷新高速缓存表项的指令时,或者当一个FLASH硬件信号产生时(通常在高速缓存不命中之后),高速缓存控制器才将高速缓存行写回到RAM中。

当cache miss时,高速缓存行被写回到内存中,如果有必要的话,把正确的行从RAM中取出放到高速缓存的表项中。

多处理器系统的每一个处理器都有一个单独的硬件高速缓存,因此它们需要额外的硬件电路用于保持高速缓存内容的同步,但是也因此更新变得更耗时:只要一个CPU修改了它的硬件高速缓存,它就必须检查同样的数据是否包含在其他的硬件高速缓存中;如果是,它必须通知其他CPU用适当的值对其更新。通常这种活动叫做高速缓存倾听(cache listening)。值得庆幸的是,这所有的一切都在硬件级处理,内核无需关心。

处理器的cr0寄存器的CD标志位是用来启动或禁用高速缓存电路,这个寄存器中的NW标志指明高速缓存是使用通写还是回写。

Linux对于所有的页框都启用高速缓存,对于写操作总是采用回写策略。

转换后援缓冲器(TLB)

除了通用硬件高速缓存之外,80x86处理器还包含了另一个称为转换后援缓冲器或TLB(Translation Lookaside Buffer)的高速缓存用于加快线性地址的转换。当一个线性地址被第一次使用时,通过慢速访问RAM中的页表计算出相应的物理地址。同时物理地址被存放在一个TLB表项(TLB entry)中,以便以后对同一个线性地址的引用可以快速地得到转换。

在多处理器系统中,每个CPU都有自己的TLB,这叫做该CPU的本地TLB。与高速硬件缓存相反,TLB中的对应项不必同步,这是应为运行在现有CPU上的进程可以使同一线性地址与不同的物理地址发生联系。

当CPU的cr3控制寄存器被修改时,硬件自动使本地TLB中的所有表项都无效,这是因为新的一组页表被启用而TLB指向的是旧数据。

Linux中的分页

Linux采用了一种同时适用于32位和64位系统的普遍分页模型。两级分页对32位系统来说已经足够,但64位系统需要更多数量的分页级别。直到2.6.10版本,Linux采用三级分页模型,从2.6.11版本开始,采用了4级分页模型。四种页表分别为:

  • 页全局目录(Page Global Directory)

    页全局目录包含若干页上级目录的地址

  • 页上级目录(Page Upper Directory)

    页上级目录又依次包含若干页中间目录的地址

  • 页中间目录(Page Middle Directory)

    页中间目录又包含若干页表项的地址

  • 页表(Page Table)

    每一个页表项都指向一个页框。

线性地址由此被分成五个部分。每一个部分的大小与具体计算机的计算机体系结构有关。

  • 32位系统:

    对于没有启用物理地址扩展(PAE)的32位系统,两级页表已经足够。Linux通过使“页上级目录位”和“页中间目录位”全为0,从根本上取消了页上级目录和页中间目录字段。不过页上级目录和页中间目录在指针序列中的位置被保留,以便同样的代码在32位系统和64位系统下都能使用。内核为页上级目录和页中间目录保留了一个位置,这是通过把它们的页目录项数设置为1,并把这两个目录项映射到全局目录的一个适当的目录项而实现的。

    启用物理地址扩展(PAE)的32位系统使用了3级页表。Linux的页全局目录对应80x86的页目录指针表(PDPT),取消了页上级目录,页中间目录对应80x86的页目录,Linux的页表对应80x86的页表。

  • 64位系统:

    64位系统使用三级还是四级分页取决于硬件对线性地址的位的划分。

Linux的进程处理很大程度上依赖于分页,事实上,线性地址到物理地址的自动转换使下面的设计目标变得可行:

  • 给每一个进程分配一块不同的物理地址空间,这确保了可以有效地防止寻址错误。
  • 区别页(即一组数据)和页框(即主存中地物理地址)之不同。这就允许存放在某一个页框中的一个页,然后保存在磁盘上,以后重新装入这同一页时又可以被装在不同地页框中。这就是虚拟内存机制的基本要素。

余下部分,为了具体起见,将涉及80x86处理器使用的分页机制。

每一个进程都有它自己的页全局目录和自己的页表集。当发生进程切换时,Linux把cr3控制寄存器的内容保存在前一个执行进程的描述符中,然后把下一个要执行进程的描述符的值装入cr3寄存器中。因此当新进程重新开始在CPU上执行时,分页单元指向一组正确的页表。

把线性地址映射到物理地址虽然有点复杂,但是现在已经成了一种机械式的任务。

下面几小节列举一些比较单调乏味的函数和宏,它们检索内核为了查找地址和管理表格所需的信息(其中大多数函数只有一两行)

线性地址字段

下列宏简化了页表处理

  • PAGE_SHIFT:

    指定Offset字段的位数。当使用80x86处理器时,它产生的值为12。由于页内所有地址都必须能放到Offset字段中,因此80x86系统的页的大小是212=4096字节。PAGE_SHIFT的值为12可以看作以2为底的页大小的对数(log24096)。这个宏由PAGE_SIZE使用以返回页的大小。最后,PAGE_MASK宏产生的值为0xfffff000,用以屏蔽Offset字段的所有位。

  • PMD_SHIFT:

    指定线性地址的Offset字段和Table字段的总位数;换句话说,是页中间目录项可以映射的区域的大小的对数。PMD_SIZE宏用于计算由页中间目录的一个单独表项所映射的区域大小,也就是一个页表的大小。PMD_MASK宏用于屏蔽Offset字段和Table字段的所有位。

    大型页不使用最后一级页表,所以产生大型页尺寸的LARGE_PAGE_SIZE宏等于PMD_SIZE,而大型页地址中用于屏蔽Offset字段和Table字段的所有位的LARGE_PAGE_MASK宏就等于PMD_MASK。

  • PUD_SHIFT:

    确定页上级目录所能映射的区域的大小的对数(Offset字段、Table字段、Middle Dir字段的位数)。PUD_SIZE宏用于计算页全局目录中的一个单独表项所能映射的区域大小。PUD_MASK宏用于屏蔽Offset字段、Table字段、Middle Dir字段的所有位。

    在80x86处理器上,PUD_SHIFT 总是等价于PMD_SHIFT,而PUD_SIZE则等于4MB或2MB。

    注意!!!这里PUD_MASK宏在《原书》中描述是“用于屏蔽Offset字段、Table字段、Middle Dir字段和Upper Dir字段的所有位”。应该没有Upper Dir才对。

  • PGDIR_SHIFT:

    确定页全局目录项能映射的区域的大小的对数(Offset字段、Table、Middle Dir以及Upper Dir字段的位数)。同样,PGDIR_SIZE宏用于计算页全局目录中的一个单独表项所能映射区域的大小。PGDIR_MASK宏用于屏蔽Offset字段、Table、Middle Dir以及Upper Dir字段的所有位。

  • PTRS_PER_PTE、PTRS_PER_PMD、PTRS_PER_PUD以及PTRS_PER_PGD

    用于计算页表、页中间目录、页上级目录和页全局目录表中表项的个数。

页表处理

pte_t、pmd_t、pud_t和pgd_t分别描述页表项、页中间目录项、页上级目录项和页全局目录项的格式。pgprot_t表示与一个单独表项相关的保护标志。

当PAE激活时它们都是64位的数据类型,否则都是32位数据类型

五个类型转换宏(__pte、__pmd、__pud、__pgd和__pgprot)把一个无符号整数转化成所需要的类型。另外五个类型转换宏(pte_val、pmd_val、pud_val、pgd_val和pgprot_val)执行相反的转换,即把上面所提到的四种特殊的类型转换成一个无符号整数。(四种??)

内核还提供了许多宏和函数用于读或修改页表表项。见下表

宏或者函数 作用
pte_none
pmd_none
pud_none
pgd_none
如果对应表项的值为0
则这些宏的值为1
否则为0
pte_clear
pmd_clear
pud_clear
pgd_clear
清除相应页表的一个表项,由此禁止进程使用由该页
表项映射的线性地址。
ptep_get_and_clear() 清除一个页表项并返回前一个值
set_pte
set_pmd
set_pud
set_pgd
set_pte_atomic
向一个页表项中写入指定的值。
其中set_pte_atomic和set_pte的作用相同
但是当PAE被激活时它同样能保证64位的
值被原子地写入。
(“原子操作(atomic operation)是不需要synchronized”,
这是多线程编程的老生常谈了。所谓原子操作是指不
会被线程调度机制打断的操作)
pte_same(a,b) 如果a和b两个页表项指向同一页并且指定相同的访
问优先级则函数返回1,否则返回0
pmd_large(e) 如果页中间目录项e指向一个大型页(2MB或4MB)
则返回1,否则返回0
pmd_bad 由函数使用并通过输入参数传递检查页中间目录项
如果目录项指向一个不能使用的页表,也就是说
1.页不在主存中(present标志被清除)
2.页只允许读访问(Read/Write标志被清除)
3.Acessed或者Dirty位被清除(对于每一个现有的页表,Linux总是强制设置这些标志)
这个宏则产生值为1,否则为0
pud_bad
pgd_bad
总是0
pte_bad 无定义,因为页表项引用一个不在主存中的页、一个不能写的页或者一个根本无法访问的页都是合法的。
pte_present 一个页表项的Present标志或者Page Size标志的值都为0,则该宏的值为0,否则为1。
pmd_present 如果相应页表项的Present值为1,则宏值为1
pud_present
pgd_present
总是1

表:读页标志的函数(略)

表:设置页标志的函数(略)

表:对页表项操作的宏(略)

当使用两级页表时,创建或删除一个页中间目录项是不重要的。页中间目录仅包含有一个指向下属页表的目录项。所以,页中间目录项只是页全局目录项的一项而已。然而当处理页表时,创建一个页表项可能很复杂,因为包含页表项的那个页表可能就不存在。在这样情况下,有必要分配一个新的框架,把它填为0,并把这个表项加入。

如果PAE被激活,内核使用三级页表。当内核创建一个新的页全局目录时,同时也分配四个相应的页中间目录;只有当父页全局目录被释放时,这四个页中间目录才得以释放。当使用两级或三级分页时,页上级目录项总是被映射为页全局目录中的一个单独项。

表:页分配函数(略)

物理内存布局

在初始化阶段,内核必须建立一个物理地址映射来指定哪些物理地址范围对内核可用而哪些不可用(或者因为它们映射设备I/O的共享内存,或者因为相应的页框含有BIOS数据)。

内核将下列页框记为保留:

  • 在不可用的物理地址范围内的页框。
  • 含有内核代码和已初始化的数据结构的页框。

保留页框中的页绝不能被动态分配或交换到磁盘上

一般来说,Linux内核安装在RAM中从物理地址0x00100000开始的地方,也就是说,从第二个MB开始。所需要的页框总数依赖于内核的配置方案:典型的配置所得到的内核可以被安装在小于3MB的RAM中。

为什么内核没有安装在RAM第一个MB开始的地方?因为PC体系结构有几个独特的地方必须考虑到。例如

  • 页框0由BIOS使用,存放加电自检(Power-On Self-Test,POST)期间检查到的系统硬件配置。因此,很多笔记本电脑的BIOS甚至在系统初始化后还将数据写到该页框。
  • 物理地址从0x000a0000到0x000fffff的范围通常留给BIOS例程,并且映射ISA图形卡上的内部内存。这个区域就是所有的IBM兼容PC上从640KB到1MB之间著名的洞:物理地址存在但被保留,对应页框不能由操作系统使用。
  • 第一个MB内的其他页框可能由特定的计算机模型保留。

在启动过程的早期阶段(参看本书附录一),内核询问BIOS并了解物理内存的大小。在新近的计算机中,内核也调用BIOS过程建立一组物理地址范围和其对应的内存类型。

随后内核执行machine_specific_memory_setup()函数,该函数建立物理地址映射(如下表)。当然如果这张表是可获取的,那是内核在BIOS列表的基础上构建的;否则内核按照保守的默认设置构建这张表:从0x9f(LOWMEMSIZE())到0x100(HIGH_MEMORY)号所有的页框都标记为保留。

开始 结束 类型
0x00000000 0x0009ffff Usable
0x000f0000 0x000fffff Reserved
0x00100000 0x07feffff Usable
0x07ff0000 0x07ff2fff ACPI data
0x07ff3000 0x07ffffff ACPI NVS
0xffff0000 0xffffffff Reserved

这张表显示了具有128MB RAM计算机的典型配置。

类型为ACPI data的地址范围中存有加电自检(POST)阶段由BIOS写入的系统硬件设备信息,在初始化阶段,内核把这些信息拷贝到一个合适的内核数据结构中,然后认为这些页框是可用的。

类型为ACPI NVS的地址范围被映射到硬件设备的ROM芯片。

最后的从oxffff0000开始的物理地址范围标记为保留,因为它由硬件映射到BIOS的ROM芯片(参见附录一)。注意BIOS也许并不提供一些物理地址范围的信息(如上表中没有的0x000a0000到ox000effff)。为了安全可靠起见,Linux假定这样的范围是不可用的。

内核可能不会见到BIOS报告所有的物理内存,例如,如果未使用PAE支持来编译,即使有更大物理内存可供使用,内核也只能寻址4GB大小的RAM。

在执行machine_specific_memory_setup()函数之后,setup_memory()函数被调用:它分析物理内存区域表并初始化一些变量来描述内核的物理内存布局。

为了避免把内核装入一组不连续的页框内,Linux更愿意跳过RAM的第一个MB明确地说,Linux用PC体系结构未保留的页框来动态的存放所分配的页框。

进程页表

进程的线性地址空间分成两部分:

  • 0x00000000到0xbfffffff,无论进程运行在用户态还是内核态都可以寻址
  • 0xc0000000到0xffffffff,只有内核态的进程才能寻址

当进程运行在用户态时,它产生的线性地址小于oxc0000000;当进程运行在内核态时,它执行内核代码,所产生的地址大于等于0xc0000000。但是,再某些情况下,内核为了检索或存放数据必须访问用户态线性地址空间。

宏PAGE_OFFSET产生的值就是0xc0000000,这就是进程在线性地址空间中的偏移量,也是内核生存空间的开始之处。

页全局目录的第一部分表项映射的线性地址小于0xc0000000,具体大小依赖于特定进程。相反剩余的表项对所有的进程来说都应该是相同的,它们等于主内核页全局目录的相应表项。

内核页表

内核维持着一组自己使用的页表,驻留在所谓的主内核页全局目录(master kernel Page Global Directory)中。在系统初始化后,这组页表还从未被任何进程或任何内核线程直接使用;更准确地说,主内核页全局目录的最高目录项部分作为参考模型,为系统每一个普通进程对应的页全局目录项提供参考模型。

内核如何确保对主内核页全局目录的修改能传递到由进程实际使用的页全局目录中?

内核如何初始化自己的页表?分为两个阶段。

实际上,内核映像刚刚被装入内存后,CPU仍然运行于实模式,所以分页功能没有被启用。

第一阶段:内核创建一个有限的地址空间,包括内核的代码段和数据段、初始页表和用于存放动态数据结构的共128KB大小的空间。这个最小限度的地址空间进能够将内核装入RAM和对其初始化的核心数据结构。(这句话······)

第二阶段:内核充分利用剩余的RAM并适当地建立分页表。

临时内核页表

临时页全局目录是在内核编译过程中静态地初始化的,而临时页表是由startup_32()汇编语言函数初始化的。(页上级目录和页中间目录相当于页全局目录项)

临时页全局目录放在 swapper_pg_dir 变量中。临时页表在 pg0 变量处开始存放,紧接在内核未初始化的数据段后面

假设内核初始化自己页表的第一阶段创建的128KB有限地址空间容纳于RAM前8MB空间里。为了映射RAM前8MB的空间,需要用到两个页表。

分页的第一个阶段的目标是允许在实模式下和保护模式下都能很容易地对这8MB寻址。因此内核必须创建一个映射,把从0x00000000到0x007fffff的线性地址和从0xc0000000到0xc07fffff的线性地址映射到从0x00000000到0x007fffff的物理地址。换句话说,内核在初始化的第一阶段,可以通过与物理地址相同的线性地址或者通过从0xc0000000开始的8MB线性地址对RAM前8MB进行寻址。

当RAM小于896MB时的最终内核页表

由内核页表所提供的最终映射必须把从0xc0000000开始的线性地址转化为从0开始的物理地址。

宏 __pa 用于把从 PAGE_OFFSET 开始的线性地址转换成相应的物理地址

宏 __va 用于相反的转化

主内核页全局目录仍然保存在 swapper_pg_dir 变量中,它由 paging_init() 函数初始化。该函数进行如下操作:

  1. 调用 pagetable_init() 适当地进阿里页表项。
  2. 把 swapper_pg_dir 的物理地址写入cr3控制寄存器中。
  3. 如果把 CPU 支持 PAE 并且如果内核编译时支持 PAE ,则将cr4控制寄存器的 PAE 标志置位
  4. 调用 __flush_tlb_all() 使 TLB(转换后援缓冲器)的所有项无效。

pagetable_init() 执行的操作既依赖于现有的 RAM 容量,也依赖于 CPU 模型。最简单的,计算机有小于896MB的 RAM ,32位物理地址足以对所有可用 RAM 进行寻址,因为没有必要激活 PAE 机制。

实际上,这最简单的情况是1GB的RAM,最高128MB留给其他映射用了,如固定映射的线性地址和非连续内存区的线性地址。

我们假定 CPU 是支持4MB页和“全局(global)” TLB 表项的最新80x86微处理器,注意如果页全局目录项对应的是0xc0000000之上的线性地址,则把所有这些项的 User/Supervisor 标志清零,由此拒绝用户态进程访问内核地址空间。还注意 Page Size 被置位使得内核可以通过使用大型页来对 RAM 进行寻址。

由 startup_32() 函数创建的物理内存前8MB的恒等映射用来完成内核的初始化阶段,当这种映射不再必要时,内核调用 zap_low_mappings() 函数清除对应的页表项。

实际上,内核也调整与“固定映射的线性地址”对应的页表项。

当RAM大小在896MB和4096MB之间时的最终内核页表

在这种情况下,并不把 RAM 全部映射到内核地址空间。Linux 在初始化阶段可以做的最好的是把一个具有896MB的 RAM 窗口(window)映射到内核线性地址空间。如果一个程序需要对现有 RAM 的其余部分寻址,那就必须把某些其他的线性地址间隔映射到所需的 RAM。这就意味着修改某些页表项的值。实际上,这是“动态重映射”

在这种情况下,内核使用与前一种情况相同的代码来初始化页全局目录。

当RAM大于4096MB时的最终内核页表

对于 RAM 大于4GB计算机的内核页表的初始化,其实分为三种情况:

  1. CPU 模型支持物理地址扩展(PAE)
  2. RAM 容量大于4GB
  3. 内核以 PAE 支持来编译

尽管 PAE 处理36位物理地址,但是线性地址依然是32位地址。如前所述,Linux 映射一个896MB的 RAM 窗口到内核线性地址空间,剩余的 RAM 由动态重映射来处理。但是在该情况下,差异主要是使用三级分页模型。

页全局目录中的前三项与用户线性地址空间相对应,内核用一个空页(empty_zero_page)的地址对这三项进行初始化。第四项用页中间目录(pmd)的地址初始化,该页中间目录是通过调用 alloc_bootmem_low_pages() 分配的。页中间目录中的前448项(共512项,但后64项留给非连续内存分配)用 RAM 前896MB的物理地址填充。

注意,支持 PAE 的所有 CPU 模型也支持大型2MB页和全局页,正如前一种情况,只要可能,Linux 使用大型页来减少页表数。

然后页全局目录的第四项被拷贝到第一项中,这样好为线性地址空间的前896MB中的地物理内存映射做镜像。为了完成对 SMP 系统的初始化,这个映射是必须的:当这个映射不再必要时,内核通过调用 zap_low_mappings() 函数来清除对应的页表项,就像先前的情况。

固定映射的线性地址

之前有讲过,有128MB的线性地址总是留作他用,因为内核使用这些线性地址实现非连续内存分配和固定映射的线性地址。

非连续内存分配仅仅是动态分配和释放内存页的一种特殊方式。

固定映射的线性地址(fix_mapped linear address)基本上是一种类似于0xffffc000这样的常量线性地址,其对应的物理地址不必等于线性地址减去0xc0000000,而是可以以任意方式建立。因此,每一个固定映射的线性地址都映射一个物理内存的页框。内核使用固定映射的线性地址来替代指针变量,因为这些指针变量的值从不改变。

固定映射的线性地址概念上类似于对 RAM 前896MB映射的线性地址。不过固定映射的线性地址可以映射任何物理地址,而由第4GB初始部分的线性地址所建立的映射是线性的(线性地址X 映射物理地址 X-PAGE_OFFSET)。

就指针变量而言,固定映射的线性地址更有效。事实上,间接引用一个指针变量比间接引用一个立即常量地址要多一次内存访问。此外,在间接引用一个指针变量之前对其值进行检查是一个良好的编程习惯;相反,对一个常量线性地址的检查则是没有必要的。

每一个固定映射的线性地址都由定义于 enum fixed_addresses 数据结构中的整形索引来表示,每一个固定映射的线性地址都存放在线性地址的第4个 GB 的末端。fix_to_virt() 函数计算从给定索引开始的常量线性地址。

为了把一个物理地址与固定映射的线性地址关联起来,内核使用 set_fixmap(idx,phys) 和 set_fixmap_nocache(idx,phys) 宏。这两个函数都把 fix_to_virt(idx) 线性地址对应的一个页表项初始化为一个物理地址 phys;不过,第二个函数也把页表项的 PCD 标志置位,因此,当访问这个页框的数据时禁用硬件高速缓存。反过来,clear_fixmap(idx) 用来撤销固定映射线性地址 idx 和物理地址之间的连接。

处理硬件高速缓存和TLB

内存寻址最后一个主题是关于内核如何使用硬件高速缓存来达到最佳效果。

内核开发者采用一些技术来减少高速缓存和TLB的为命中次数。

处理硬件高速缓存

如前所述,硬件高速缓存通过高速缓存行(cache)寻址的,L1_CACHE_BYTES宏产生以字节为单位的高速缓存行的大小。(在 Pentium 4 上,它的值为128)

为了使高速缓存的命中率达到最优化,内核在下列决策中考虑体系结构:

  • 一个数据结构中最常使用的字段放在该数据结构内的低偏移部分,以便它们能够处于高速缓存的同一行中。
  • 当为一大组数据结构分配空间时,内核试图把它们都存放在内存中,以便所有高速缓存行按同一方式使用。

80x86微处理器自动处理高速缓存的同步,所以应用于这种处理器的 Linux 内核并不处理任何硬件高速缓存的刷新。不过内核却为不能同步高速缓存的处理器提供了高速缓存的刷新接口。

处理TLB

处理器不能自动同步它们自己的TLB高速缓存,因为决定线性地址和物理地址之间的映射何时不再有效的是内核,而不是硬件。

Linux 2.6 提供了几种在合适时机应当运用的TLB刷新方法,这取决于页表更换的类型。

这个小节中的“同步”“刷新”“无效”,是否是同一个意思。

这本书的翻译感觉不是很友好,或许技术类专著都这样吧,会翻译的技术不行,技术行的应该没有翻译问题吧。

表:独立于系统的使TLB表项无效的方法

方法名称 说明 典型应用时机
flush_tlb_all 刷新所有TLB表项(包括那些全局页所对应的TLB表项,即那些Global标志被置位的页) 改变内核页表项时
flush_tlb_kernel_range 刷新给定线性地址范围内的所有TLB表项(包括那些全局页对应的TLB表项) 更换一个范围内的内核页表项时
flush_tlb 刷新当前进程拥有的非全局页相关的所有TLB表项 执行进程切换时
flush_tlb_mm 刷新指定进程拥有的非全局页相关的所有TLB表项 创建一个新的子进程时
flush_tlb_range 刷新指定进程的线性地址间隔对应的TLB表项 释放某个进程的线性地址间隔时
flush_tlb_pgtables 刷新指定进程中特定的相临页表集相关的TLB表项 释放进程的一些页表时
flush_tlb_page 刷新指定进程中单个页表项相关的TLB表项 处理缺页异常时

尽管普通 Linux 内核提供了丰富的 TLB 方法,但是通常每一个微处理器都提供了更受限制的一组使 TLB 无效的汇编指令。在这一方面,一个更为灵活的硬件平台就是 Sun 的 UltraSPARC。相比之下,Intel 微处理器只提供了两种使 TLB 无效的技术:

  1. 在向 cr3 寄存器写入值时,所有的 Pentium 处理器自动刷新相对于非全局页的 TLB 表项。
  2. 在 Pentium Pro 及以后的处理器中,invlpg 汇编语言指令使映射指定线性地址的单个 TLB 表项无效。

表:应用这种硬件技术的 Linux 宏(略);这些宏是实现独立于系统的方法

注意:在80x86系统中,当页表与父页表解除链接时什么也不需要做。

独立于体系结构的使 TLB 无效的方法非常简单地扩展到了多处理器系统上。在一个 CPU 上运行的函数发送一个处理器间中断给其他的 CPU 来强制执行它们执行适当的函数使 TLB 无效。

一般来说,任何进程切换都会暗示着更换活动页表集。

相对于过期页表,本地 TLB 表项必须被刷新;这个过程在内核把新的页全局目录的地址写入 cr3 控制寄存器时会自动完成。不过内核在下列情况下将避免 TLB 被刷新:

  • 当两个使用相同页表集的普通进程之间执行进程切换时。
  • 当在一个普通进程和一个内核线程间执行进程切换时:内核线程并不拥有自己的页表集,确切地说,它们使用刚在 CPU 上执行过的普通进程的页表集。

除了进程切换之外,还有其他几种情况下内核需要刷新 TLB 中的一些表项。例如,当内核为某个用户态进程分配页框并将它的物理地址存入页表项时,它必须刷新与之相应线性地址对应的任何本地 TLB 表项。在多处理器系统中,如果有多个 CPU 在使用相同的页表集,那么内核还必须刷新这些 CPU 上使用的相同页表集的 TLB 表项。

为了避免多处理器系统上无用的 TLB 刷新,内核使用一种叫做懒惰 TLB(lazy TLB)模式的技术。其基本思想是,如果几个CPU正在使用相同的页表,而且必须对这些 CPU 上的一个 TLB 表项刷新,那么,在某些情况下,正在运行内核线程的那些 CPU 上的刷新就可以延迟。

P82,具体实现过程和所使用的数据结构,云里雾里
单地扩展到了多处理器系统上。在一个 CPU 上运行的函数发送一个处理器间中断给其他的 CPU 来强制执行它们执行适当的函数使 TLB 无效。

一般来说,任何进程切换都会暗示着更换活动页表集。

相对于过期页表,本地 TLB 表项必须被刷新;这个过程在内核把新的页全局目录的地址写入 cr3 控制寄存器时会自动完成。不过内核在下列情况下将避免 TLB 被刷新:

  • 当两个使用相同页表集的普通进程之间执行进程切换时。
  • 当在一个普通进程和一个内核线程间执行进程切换时:内核线程并不拥有自己的页表集,确切地说,它们使用刚在 CPU 上执行过的普通进程的页表集。

除了进程切换之外,还有其他几种情况下内核需要刷新 TLB 中的一些表项。例如,当内核为某个用户态进程分配页框并将它的物理地址存入页表项时,它必须刷新与之相应线性地址对应的任何本地 TLB 表项。在多处理器系统中,如果有多个 CPU 在使用相同的页表集,那么内核还必须刷新这些 CPU 上使用的相同页表集的 TLB 表项。

为了避免多处理器系统上无用的 TLB 刷新,内核使用一种叫做懒惰 TLB(lazy TLB)模式的技术。其基本思想是,如果几个CPU正在使用相同的页表,而且必须对这些 CPU 上的一个 TLB 表项刷新,那么,在某些情况下,正在运行内核线程的那些 CPU 上的刷新就可以延迟。

P82,具体实现过程和所使用的数据结构,云里雾里

笔记:深入理解Linux内核(二)相关推荐

  1. tipi 深入理解php内核 pdf_大牛的学习笔记-深入理解Linux内核(完整版)

    第一章.绪论 1.Unix文件可以是下列类型之一: a.正规文件(regular file) b.目录(directroy) c.符号链(symbolic link) d.块设备文件(block-or ...

  2. 《深入理解Linux内核》 读书笔记

    深入理解Linux内核 读书笔记 一.概论 操作系统基本概念 多用户系统 允许多个用户登录系统,不同用户之间的有私有的空间 用户和组 每个用于属于一个组,组的权限和其他人的权限,和拥有者的权限不一样. ...

  3. 深入理解LINUX内核(影印版第3版)》的笔记

    书名: 深入理解LINUX内核(影印版第3版) 作者: Daniel P.Bovet/Marco Cesati 副标题: Understanding the Linux Kernel 页数: 923 ...

  4. 深入理解Linux内核第3版--笔记-1.pdf

    深入理解Linux内核第3版.pdf         Understanding the Linux Kernel, 3rd Edition Preface    The Audience for T ...

  5. 深入理解Linux内核之内存寻址

    说明: 本文基于第三版<深入理解 Linux 内核>,该部分以 80x86 处理器为基准进行介绍,并且略过了原文中详细介绍32位扩展分页部分. https://xcraft.tech/20 ...

  6. [转]深入理解linux内核list_head

    http://blog.chinaunix.net/uid-27122224-id-3277511.html 深入理解linux内核list_head的实现 2012-07-17 17:37:01 分 ...

  7. 深入理解Linux内核-第3版 译者序、前言、目录 内核2.6.11

    一.译者序 Linux是一个全新的世界,世界意味着博大精深,而新或许代表对旧的割舍和扬弃,加在一起,就是要我们在割舍和扬弃的同时还要积累知识到博大精深的地步,这容易做到吗?是的,这不容易做到.Gera ...

  8. 深入理解 Linux 内核

    Linux 内核系列文章 Linux 内核设计与实现 深入理解 Linux 内核 深入理解 Linux 内核(二) Linux 设备驱动程序 Linux设备驱动开发详解 文章目录 Linux 内核系列 ...

  9. 深入理解Linux内核-内存管理

    页框管理 内核对整个物理内存进行分页,每页大小为4KB或者4MB(大小无所谓,不同os都可能不一样),一般认为Linux的页大小为4KB,内核必须记录好每个页框的信息,所以linux内核把所有的页框都 ...

最新文章

  1. 2021年大数据ELK(二十二):采集Apache Web服务器日志
  2. 深入DataGrid分页样式实例
  3. 六、jQuery基础
  4. 235. 二叉搜索树的最近公共祖先
  5. Python - WebDriver 识别登录验证码
  6. IEEE 迎来首位华人主席,马里兰大学终身教授刘国瑞当选
  7. vscode中前端vue项目详解_web前端Vue项目实战-Music
  8. 麒麟系统常见问题详解
  9. 蓝桥杯 回文数 C语言版
  10. 【Unity】 Unity主题皮肤
  11. 【SIFT算法】极值检测关键点精确定位
  12. SX1278 Lora网关
  13. RDS-TMC(Traffic Message Channel)蕴藏的商机不可小视
  14. 小白零基础学习Java编程好学吗?
  15. input:-webkit-autofill
  16. hiredis初步使用
  17. FTP voyager使用配置参考
  18. How to solve Exception raised during rendering: java.lang.System.arraycopy([CI[CII)V in Android
  19. Array.isArray() 判断是不是数组
  20. 什么是二阶滤波器?有什么优点?

热门文章

  1. python做一个销售管理系统_python实现手机销售管理系统
  2. 【数据库】Oracle中的复杂数据处理
  3. 个人经典音乐收藏(七)Encore une fois,Helene Segara
  4. 沉入海底 2 年的微软数据中心浮出水面:故障率只有陆地上的 1/8,除了长点贝类和藻类完全没问题...
  5. Ideas插件的安装
  6. 移动web开发问题和优化小结
  7. 特里·杜吉分享的EDM营销经验
  8. c语言fread函数,C语言“fread”函数的用法?
  9. Pandas的Series统计函数(7)
  10. mac网页java无法加载,java – 无法加载共享库GDX Freetype Mac