Large files (moderate)
实验目标
给 xv6 的 inode 增加二级间接块(doubly-indirect block) ,把单文件上限从 268 块提升到 65803 块。
官方 handout 原文(The problem):
"Currently xv6 files are limited to 268 blocks, or 268*BSIZE bytes (BSIZE is 1024 in xv6). This limit comes from the fact that an xv6 inode contains 12 'direct' block numbers and one 'singly-indirect' block number, which refers to a block that holds up to 256 more block numbers, for a total of 12+256=268 blocks."
官方 handout 原文(The solution):"You'll change the xv6 file system code to support a 'doubly-indirect' block in each inode, containing 256 addresses of singly-indirect blocks, each of which can contain up to 256 addresses of data blocks. The result will be that a file will be able to consist of up to 65803 blocks, or 256*256+256+11 blocks (11 instead of 12, because we will sacrifice one of the direct block numbers for the double-indirect block)."
验收标准 :bigfile 写出 65803 块,且 usertests 全部通过。
前置知识
1. 改造前后:inode 的 addrs[] 怎么用
改造前:12 直接 + 1 一级间接 = 13 个槽位
#mermaid-svg-eNNCWmyu2u82aErb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-eNNCWmyu2u82aErb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-eNNCWmyu2u82aErb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-eNNCWmyu2u82aErb .error-icon{fill:#552222;}#mermaid-svg-eNNCWmyu2u82aErb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eNNCWmyu2u82aErb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-eNNCWmyu2u82aErb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eNNCWmyu2u82aErb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eNNCWmyu2u82aErb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-eNNCWmyu2u82aErb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eNNCWmyu2u82aErb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eNNCWmyu2u82aErb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eNNCWmyu2u82aErb .marker.cross{stroke:#333333;}#mermaid-svg-eNNCWmyu2u82aErb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eNNCWmyu2u82aErb p{margin:0;}#mermaid-svg-eNNCWmyu2u82aErb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-eNNCWmyu2u82aErb .cluster-label text{fill:#333;}#mermaid-svg-eNNCWmyu2u82aErb .cluster-label span{color:#333;}#mermaid-svg-eNNCWmyu2u82aErb .cluster-label span p{background-color:transparent;}#mermaid-svg-eNNCWmyu2u82aErb .label text,#mermaid-svg-eNNCWmyu2u82aErb span{fill:#333;color:#333;}#mermaid-svg-eNNCWmyu2u82aErb .node rect,#mermaid-svg-eNNCWmyu2u82aErb .node circle,#mermaid-svg-eNNCWmyu2u82aErb .node ellipse,#mermaid-svg-eNNCWmyu2u82aErb .node polygon,#mermaid-svg-eNNCWmyu2u82aErb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-eNNCWmyu2u82aErb .rough-node .label text,#mermaid-svg-eNNCWmyu2u82aErb .node .label text,#mermaid-svg-eNNCWmyu2u82aErb .image-shape .label,#mermaid-svg-eNNCWmyu2u82aErb .icon-shape .label{text-anchor:middle;}#mermaid-svg-eNNCWmyu2u82aErb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-eNNCWmyu2u82aErb .rough-node .label,#mermaid-svg-eNNCWmyu2u82aErb .node .label,#mermaid-svg-eNNCWmyu2u82aErb .image-shape .label,#mermaid-svg-eNNCWmyu2u82aErb .icon-shape .label{text-align:center;}#mermaid-svg-eNNCWmyu2u82aErb .node.clickable{cursor:pointer;}#mermaid-svg-eNNCWmyu2u82aErb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-eNNCWmyu2u82aErb .arrowheadPath{fill:#333333;}#mermaid-svg-eNNCWmyu2u82aErb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-eNNCWmyu2u82aErb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-eNNCWmyu2u82aErb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eNNCWmyu2u82aErb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-eNNCWmyu2u82aErb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eNNCWmyu2u82aErb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-eNNCWmyu2u82aErb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-eNNCWmyu2u82aErb .cluster text{fill:#333;}#mermaid-svg-eNNCWmyu2u82aErb .cluster span{color:#333;}#mermaid-svg-eNNCWmyu2u82aErb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-eNNCWmyu2u82aErb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-eNNCWmyu2u82aErb rect.text{fill:none;stroke-width:0;}#mermaid-svg-eNNCWmyu2u82aErb .icon-shape,#mermaid-svg-eNNCWmyu2u82aErb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eNNCWmyu2u82aErb .icon-shape p,#mermaid-svg-eNNCWmyu2u82aErb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-eNNCWmyu2u82aErb .icon-shape .label rect,#mermaid-svg-eNNCWmyu2u82aErb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eNNCWmyu2u82aErb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-eNNCWmyu2u82aErb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-eNNCWmyu2u82aErb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 改造前 · addrs 共 13 个槽位
每个槽 = 1 个数据块号
每项 = 1 个数据块号
addrs0..11
直接块 ×12
addrs12
一级间接块
数据块 ×12
12 KB
一级间接表
1024B ÷ 4B = 256 项
数据块 ×256
256 KB
合计 12 + 256 = 268 块
= 268 KB
改造后:11 直接 + 1 一级间接 + 1 二级间接 = 13 个槽位
#mermaid-svg-NvjztJumsEOdAXot{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NvjztJumsEOdAXot .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NvjztJumsEOdAXot .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NvjztJumsEOdAXot .error-icon{fill:#552222;}#mermaid-svg-NvjztJumsEOdAXot .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NvjztJumsEOdAXot .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NvjztJumsEOdAXot .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NvjztJumsEOdAXot .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NvjztJumsEOdAXot .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NvjztJumsEOdAXot .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NvjztJumsEOdAXot .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NvjztJumsEOdAXot .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NvjztJumsEOdAXot .marker.cross{stroke:#333333;}#mermaid-svg-NvjztJumsEOdAXot svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NvjztJumsEOdAXot p{margin:0;}#mermaid-svg-NvjztJumsEOdAXot .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NvjztJumsEOdAXot .cluster-label text{fill:#333;}#mermaid-svg-NvjztJumsEOdAXot .cluster-label span{color:#333;}#mermaid-svg-NvjztJumsEOdAXot .cluster-label span p{background-color:transparent;}#mermaid-svg-NvjztJumsEOdAXot .label text,#mermaid-svg-NvjztJumsEOdAXot span{fill:#333;color:#333;}#mermaid-svg-NvjztJumsEOdAXot .node rect,#mermaid-svg-NvjztJumsEOdAXot .node circle,#mermaid-svg-NvjztJumsEOdAXot .node ellipse,#mermaid-svg-NvjztJumsEOdAXot .node polygon,#mermaid-svg-NvjztJumsEOdAXot .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NvjztJumsEOdAXot .rough-node .label text,#mermaid-svg-NvjztJumsEOdAXot .node .label text,#mermaid-svg-NvjztJumsEOdAXot .image-shape .label,#mermaid-svg-NvjztJumsEOdAXot .icon-shape .label{text-anchor:middle;}#mermaid-svg-NvjztJumsEOdAXot .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NvjztJumsEOdAXot .rough-node .label,#mermaid-svg-NvjztJumsEOdAXot .node .label,#mermaid-svg-NvjztJumsEOdAXot .image-shape .label,#mermaid-svg-NvjztJumsEOdAXot .icon-shape .label{text-align:center;}#mermaid-svg-NvjztJumsEOdAXot .node.clickable{cursor:pointer;}#mermaid-svg-NvjztJumsEOdAXot .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NvjztJumsEOdAXot .arrowheadPath{fill:#333333;}#mermaid-svg-NvjztJumsEOdAXot .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NvjztJumsEOdAXot .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NvjztJumsEOdAXot .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NvjztJumsEOdAXot .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NvjztJumsEOdAXot .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NvjztJumsEOdAXot .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NvjztJumsEOdAXot .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NvjztJumsEOdAXot .cluster text{fill:#333;}#mermaid-svg-NvjztJumsEOdAXot .cluster span{color:#333;}#mermaid-svg-NvjztJumsEOdAXot div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NvjztJumsEOdAXot .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NvjztJumsEOdAXot rect.text{fill:none;stroke-width:0;}#mermaid-svg-NvjztJumsEOdAXot .icon-shape,#mermaid-svg-NvjztJumsEOdAXot .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NvjztJumsEOdAXot .icon-shape p,#mermaid-svg-NvjztJumsEOdAXot .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NvjztJumsEOdAXot .icon-shape .label rect,#mermaid-svg-NvjztJumsEOdAXot .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NvjztJumsEOdAXot .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NvjztJumsEOdAXot .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NvjztJumsEOdAXot :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 改造后 · addrs 依然是 13 个槽位
每个槽 = 1 个数据块号
每项 = 1 个数据块号
每项 = 1 个数据块号
addrs0..10
直接块 ×11
addrs11
一级间接块
addrs12
二级间接块
数据块 ×11
11 KB
一级间接表
256 项
数据块 ×256
256 KB
一级表
256 项,每项指向一个二级表
二级表 ×256
每表 256 项
数据块 ×65536
65536 KB
合计 11 + 256 + 65536
= 65803 块 = 65803 KB
二级间接块的两级查找(含索引公式):
#mermaid-svg-HImXWpwBmqiV80iJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HImXWpwBmqiV80iJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HImXWpwBmqiV80iJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HImXWpwBmqiV80iJ .error-icon{fill:#552222;}#mermaid-svg-HImXWpwBmqiV80iJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HImXWpwBmqiV80iJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HImXWpwBmqiV80iJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HImXWpwBmqiV80iJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HImXWpwBmqiV80iJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HImXWpwBmqiV80iJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HImXWpwBmqiV80iJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HImXWpwBmqiV80iJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HImXWpwBmqiV80iJ .marker.cross{stroke:#333333;}#mermaid-svg-HImXWpwBmqiV80iJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HImXWpwBmqiV80iJ p{margin:0;}#mermaid-svg-HImXWpwBmqiV80iJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-HImXWpwBmqiV80iJ .cluster-label text{fill:#333;}#mermaid-svg-HImXWpwBmqiV80iJ .cluster-label span{color:#333;}#mermaid-svg-HImXWpwBmqiV80iJ .cluster-label span p{background-color:transparent;}#mermaid-svg-HImXWpwBmqiV80iJ .label text,#mermaid-svg-HImXWpwBmqiV80iJ span{fill:#333;color:#333;}#mermaid-svg-HImXWpwBmqiV80iJ .node rect,#mermaid-svg-HImXWpwBmqiV80iJ .node circle,#mermaid-svg-HImXWpwBmqiV80iJ .node ellipse,#mermaid-svg-HImXWpwBmqiV80iJ .node polygon,#mermaid-svg-HImXWpwBmqiV80iJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HImXWpwBmqiV80iJ .rough-node .label text,#mermaid-svg-HImXWpwBmqiV80iJ .node .label text,#mermaid-svg-HImXWpwBmqiV80iJ .image-shape .label,#mermaid-svg-HImXWpwBmqiV80iJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-HImXWpwBmqiV80iJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HImXWpwBmqiV80iJ .rough-node .label,#mermaid-svg-HImXWpwBmqiV80iJ .node .label,#mermaid-svg-HImXWpwBmqiV80iJ .image-shape .label,#mermaid-svg-HImXWpwBmqiV80iJ .icon-shape .label{text-align:center;}#mermaid-svg-HImXWpwBmqiV80iJ .node.clickable{cursor:pointer;}#mermaid-svg-HImXWpwBmqiV80iJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HImXWpwBmqiV80iJ .arrowheadPath{fill:#333333;}#mermaid-svg-HImXWpwBmqiV80iJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HImXWpwBmqiV80iJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HImXWpwBmqiV80iJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HImXWpwBmqiV80iJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HImXWpwBmqiV80iJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HImXWpwBmqiV80iJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HImXWpwBmqiV80iJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HImXWpwBmqiV80iJ .cluster text{fill:#333;}#mermaid-svg-HImXWpwBmqiV80iJ .cluster span{color:#333;}#mermaid-svg-HImXWpwBmqiV80iJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HImXWpwBmqiV80iJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HImXWpwBmqiV80iJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-HImXWpwBmqiV80iJ .icon-shape,#mermaid-svg-HImXWpwBmqiV80iJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HImXWpwBmqiV80iJ .icon-shape p,#mermaid-svg-HImXWpwBmqiV80iJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HImXWpwBmqiV80iJ .icon-shape .label rect,#mermaid-svg-HImXWpwBmqiV80iJ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HImXWpwBmqiV80iJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HImXWpwBmqiV80iJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HImXWpwBmqiV80iJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} i = bn / NINDIRECT
i = bn / NINDIRECT
...
j = bn % NINDIRECT
j = bn % NINDIRECT
j = bn % NINDIRECT
ip->addrsNDIRECT+1
二级间接块
一级表 a\[\]
256 项
a0 → 二级表
256 个数据块号
a1 → 二级表
256 个数据块号
a255 → 二级表
256 个数据块号
逻辑块 0..255
逻辑块 256..511
逻辑块 65280..65535
于是"二级间接区内的相对块号 bn"的拆法是:
c
i = bn / NINDIRECT; // 落在哪个二级表(一级表的下标)
j = bn % NINDIRECT; // 在该二级表内的第几项
2. bmap() 里的两种块号
handout 特意点名:
"
bmap()deals with two kinds of block numbers. Thebnargument is a 'logical block number' -- a block number within the file... The block numbers inip->addrs[], and the argument tobread(), are disk block numbers."
bmap(ip, bn) = 把「文件内第 bn 个逻辑块」翻译成「磁盘块号」,没有就 balloc 一个。 它同时被读和写调用 ------ 写时才触发分配,读时只做翻译。
3. 关键约束:磁盘 inode 的大小不能变
handout:"you're not allowed to change the size of an on-disk inode"。
算一下就明白这个约束其实被巧妙满足了:
c
struct dinode {
short type, major, minor, nlink; // 4 × 2B = 8B
uint size; // 4B
uint addrs[NDIRECT+2]; // 13 × 4B = 52B
}; // 合计 64 字节
改造前是 addrs[NDIRECT+1](NDIRECT=12 → 13 项),改造后是 addrs[NDIRECT+2](NDIRECT=11 → 13 项)。槽位总数都是 13,struct dinode 恒为 64 字节。
所以 IPB = BSIZE / sizeof(struct dinode) 不变、每块装的 inode 数不变、磁盘布局不变 ------ 只是最后一个槽位的"含义"从直接块变成了二级间接块。这就是"牺牲一个直接块"的全部代价(1 块 = 1 KB)。
4. 磁盘容量:FSSIZE 已经够用
65803 个数据块远超默认文件系统容量,所以本实验的仓库预先把 FSSIZE 设成了 200000 (kernel/param.h:16),make 时会看到:
bash
nmeta 70 (boot, super, log blocks 30 inode blocks 13, bitmap blocks 25) blocks 199930 total 200000
70 个元数据块 + 199930 个数据块 ------ 装得下 65803 块的大文件。你不需要改它,但要知道它在哪、为什么够。
提醒:handout 特意强调 改动
NDIRECT后必须make clean重建fs.img,因为mkfs也用了NDIRECT来构建文件系统。旧镜像里的 inode 还是旧布局,不重建必崩。
实现思路
四步,改动量极小:
kernel/fs.h:NDIRECT12 → 11;MAXFILE加上NINDIRECT*NINDIRECT;addrs[]改成NDIRECT+2。kernel/file.h:内存 inode 的addrs[]同步改成NDIRECT+2(两个结构体的addrs[]长度必须一致)。kernel/fs.c: bmap():在原有"直接 + 一级间接"两段之后,加一段两级查表的二级间接分支。kernel/fs.c: itrunc():对称地加一段释放逻辑 ------ 先释放所有二级表指向的数据块,再释放二级表本身,最后释放一级表。
代码实现
kernel/fs.h ------ 改三个宏和一个数组长度
c
/*
* kernel/fs.h
*/
#define NDIRECT 11
#define NINDIRECT (BSIZE / sizeof(uint))
#define MAXFILE (NDIRECT + NINDIRECT + NINDIRECT*NINDIRECT)
// On-disk inode structure
struct dinode {
short type; // File type
short major; // Major device number (T_DEVICE only)
short minor; // Minor device number (T_DEVICE only)
short nlink; // Number of links to inode in file system
uint size; // Size of file (bytes)
uint addrs[NDIRECT+2]; // Data block addresses
};
MAXFILE = 11 + 256 + 65536 = 65803,正好是bigfile要写出的块数。
kernel/file.h ------ 内存 inode 同步
c
/*
* kernel/file.h
*/
// in-memory copy of an inode
struct inode {
uint dev; // Device number
uint inum; // Inode number
int ref; // Reference count
struct sleeplock lock; // protects everything below here
int valid; // inode has been read from disk?
short type; // copy of disk inode
short major;
short minor;
short nlink;
uint size;
uint addrs[NDIRECT+2];
};
handout 原文提醒:"If you change the definition of
NDIRECT, you'll probably have to change the declaration ofaddrs[]instruct inodeinfile.h. Make sure thatstruct inodeandstruct dinodehave the same number of elements in theiraddrs[]arrays."两个结构体必须同时改 ------ 只改一个的话,
iupdate()在磁盘 inode 和内存 inode 之间拷贝时长度不匹配,文件数据会静默错位。
kernel/fs.c ------ bmap() 加二级间接分支
c
/*
* kernel/fs.c
*/
static uint
bmap(struct inode *ip, uint bn)
{
uint addr, *a;
struct buf *bp;
// 直接块(0~NDIRECT-1)
if(bn < NDIRECT){
if((addr = ip->addrs[bn]) == 0)
ip->addrs[bn] = addr = balloc(ip->dev);
return addr;
}
bn -= NDIRECT;
// 一级间接块(NDIRECT)
if(bn < NINDIRECT){
// Load indirect block, allocating if necessary.
if((addr = ip->addrs[NDIRECT]) == 0)
ip->addrs[NDIRECT] = addr = balloc(ip->dev);
bp = bread(ip->dev, addr);
a = (uint*)bp->data;
if((addr = a[bn]) == 0){
a[bn] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp);
return addr;
}
// 二级间接块(NDIRECT+1)
bn -= NINDIRECT;
if (bn < NINDIRECT*NINDIRECT) {
// 获取一级间接块的磁盘块号
if ((addr = ip->addrs[NDIRECT+1]) == 0)
ip->addrs[NDIRECT+1] = addr = balloc(ip->dev);
bp = bread(ip->dev, addr); // "一级间接"对应的 buffer
a = (uint*)bp->data;
// 获取"二级间接块"对应的磁盘块号
if ((addr = a[bn/NINDIRECT]) == 0) {
a[bn/NINDIRECT] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp);
bp = bread(ip->dev, addr); // "二级间接"对应的buffer
a = (uint*)bp->data;
// 获取最终的磁盘块号
if ((addr = a[bn%NINDIRECT]) == 0) {
a[bn%NINDIRECT] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp);
return addr;
}
panic("bmap: out of range");
}
二级间接这一段就是两次"读表 → 查表 → 缺则分配",和一级间接完全同构,只是多了一层:
- 第一次 :用
ip->addrs[NDIRECT+1]读出一级表,查a[bn/NINDIRECT]得到二级表的磁盘块号;- 第二次 :用这个块号读出二级表,查
a[bn%NINDIRECT]得到最终数据块的磁盘块号。三个必须注意的点:
- 每级都只在需要时才
balloc------ handout:"You should allocate indirect blocks and doubly-indirect blocks only as needed, like the originalbmap()." 小文件不该白白吃掉两个间接块。- 每次改了间接块的内容都必须
log_write(bp)------ 间接块是磁盘上的数据,不写日志就丢失崩溃一致性;而ip->addrs[]的修改靠iupdate(ip)(调用方负责)。- 每个
bread()都要配对brelse()------ handout 单独列为一条 hint。这里两块 buffer 先后使用、各自brelse,不能漏。
kernel/fs.c ------ itrunc() 对称释放
c
/*
* kernel/fs.c
*/
// Truncate inode (discard contents).
// Caller must hold ip->lock.
void
itrunc(struct inode *ip)
{
int i, j;
struct buf *bp, *bbp;
uint *a, *aa;
// 直接块
for(i = 0; i < NDIRECT; i++){
if(ip->addrs[i]){
bfree(ip->dev, ip->addrs[i]);
ip->addrs[i] = 0;
}
}
// 一级间接块
if(ip->addrs[NDIRECT]){
bp = bread(ip->dev, ip->addrs[NDIRECT]);
a = (uint*)bp->data;
for(j = 0; j < NINDIRECT; j++){
if(a[j])
bfree(ip->dev, a[j]);
}
brelse(bp);
bfree(ip->dev, ip->addrs[NDIRECT]);
ip->addrs[NDIRECT] = 0;
}
// 二级间接块
if (ip->addrs[NDIRECT+1]) {
bp = bread(ip->dev, ip->addrs[NDIRECT+1]);
a = (uint*)bp->data;
for (i = 0; i < NINDIRECT; i++) {
if (a[i]) {
bbp = bread(ip->dev, a[i]);
aa = (uint*)bbp->data;
for (j = 0; j < NINDIRECT; j++) {
if (aa[j])
bfree(ip->dev, aa[j]);
}
brelse(bbp);
bfree(ip->dev, a[i]);
}
}
brelse(bp);
bfree(ip->dev, ip->addrs[NDIRECT+1]);
ip->addrs[NDIRECT+1] = 0;
}
ip->size = 0;
iupdate(ip);
}
释放顺序就是
bmap分配顺序的逆序,三层嵌套:
1. 直接块 addrs[0..NDIRECT-1] → bfree 2. 一级间接:先 bfree 它指向的 256 个数据块 → 再 bfree 它自己 3. 二级间接:对每个 a[i] 先 bfree a[i] 指向的二级表里的 256 个数据块 再 bfree 二级表 a[i] 自己 循环结束后 bfree 一级表 addrs[NDIRECT+1] 自己必须先释放子块、再释放父表 ------ 顺序反了,父表的磁盘块被回收后可能被别人分配走,你就再也读不到子块的块号了,那些数据块永久泄漏。
验证
bash
make clean && make qemu
bash
$ bigfile
..........................................................................................................................................................................................................................................................................................................................................................................................................................................................................
wrote 65803 blocks
bigfile done; ok
bigfile至少要跑一分半钟。
usertests 应同样全过(handout 要求 bigfile + usertests 同时通过才算完成)。
复盘
三个核心点
- "牺牲一个直接块"换来了 65536 块 ------ 槽位总数恒为 13(磁盘 inode 恒 64 字节),只是把最后一个槽从"直接块"改成"二级间接块"。代价 1 块,收益 65536 块。这是"索引深度换容量"的典型权衡:每加一级,容量乘以 256,代价是多一次磁盘读。
bn / NINDIRECT和bn % NINDIRECT是二级索引的全部数学 ------ 前者选"哪个二级表",后者选"表内第几项"。想清楚这个,代码就是一级间接的平铺展开。bmap和itrunc必须成对改 ------ 一个管建、一个管拆。handout 最后一条 hint 就是 "Make sureitruncfrees all blocks of a file, including double-indirect blocks" 。漏改itrunc在大文件删除时会泄漏上万个磁盘块。
与真实文件系统的对照(面试加分)
| xv6(本实验后) | Linux ext2/ext3 | Linux ext4 | |
|---|---|---|---|
| 索引方式 | 11 直接 + 1 一级 + 1 二级 | 12 直接 + 1 一级 + 1 二级 + 1 三级 | Extent(区间)+ B-tree |
| 大文件代价 | 二级要 2 次额外读盘 | 三级要 3 次 | 连续块只记 (起始块, 长度),几乎 O(1) |
| 稀疏文件 | 支持(未分配的表项为 0) | 支持 | 支持 |
为什么 ext4 要换成 extent? 因为"每个数据块都要一个 4 字节索引项"在大文件下太浪费:一个 1 GB 文件需要 262144 个索引项 = 1 MB 元数据,且随机访问要多次读盘。extent 把「连续 N 块」压缩成一条记录,元数据量和查找次数都降一个数量级。这正是从"索引块"到"区间"的演进动机。
收获
- 多级索引 = 用时间换空间:间接块让小文件的 inode 保持紧凑(只有用到的才分配),大文件则通过增加一次磁盘访问换取巨大容量。
- "文件系统的元数据也是数据" :间接块本身就是磁盘块,也要
balloc/bfree,改了也要log_write。理解这一点,itrunc的释放顺序为什么必须"先子后父"就一目了然。 mkfs与内核共享NDIRECT:改了NDIRECT不重建fs.img,新旧布局混在一起会以最诡异的方式崩溃 ------ 这是本实验最容易浪费时间的一个坑(make clean解决)。
至此 xv6 文件系统的"大文件"改造完成。同一个 Lab 还有
Symbolic links(软链接,需要新增symlink系统调用、T_SYMLINK文件类型、O_NOFOLLOW标志,并让open递归跟随链接且检测环路),是路径名解析机制的完整实战。