MIT 6.S081 file system 实验篇(lab9):Large files (moderate)

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. The bn argument is a 'logical block number' -- a block number within the file... The block numbers in ip->addrs[], and the argument to bread(), 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 设成了 200000kernel/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 还是旧布局,不重建必崩。


实现思路

四步,改动量极小:

  1. kernel/fs.hNDIRECT 12 → 11;MAXFILE 加上 NINDIRECT*NINDIRECTaddrs[] 改成 NDIRECT+2
  2. kernel/file.h :内存 inode 的 addrs[] 同步改成 NDIRECT+2(两个结构体的 addrs[] 长度必须一致)。
  3. kernel/fs.c: bmap() :在原有"直接 + 一级间接"两段之后,加一段两级查表的二级间接分支。
  4. 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 of addrs[] in struct inode in file.h. Make sure that struct inode and struct dinode have the same number of elements in their addrs[] 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] 得到最终数据块的磁盘块号

三个必须注意的点:

  1. 每级都只在需要时才 balloc ------ handout:"You should allocate indirect blocks and doubly-indirect blocks only as needed, like the original bmap()." 小文件不该白白吃掉两个间接块。
  2. 每次改了间接块的内容都必须 log_write(bp) ------ 间接块是磁盘上的数据,不写日志就丢失崩溃一致性;而 ip->addrs[] 的修改靠 iupdate(ip)(调用方负责)。
  3. 每个 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 同时通过才算完成)。


复盘

三个核心点

  1. "牺牲一个直接块"换来了 65536 块 ------ 槽位总数恒为 13(磁盘 inode 恒 64 字节),只是把最后一个槽从"直接块"改成"二级间接块"。代价 1 块,收益 65536 块。这是"索引深度换容量"的典型权衡:每加一级,容量乘以 256,代价是多一次磁盘读。
  2. bn / NINDIRECTbn % NINDIRECT 是二级索引的全部数学 ------ 前者选"哪个二级表",后者选"表内第几项"。想清楚这个,代码就是一级间接的平铺展开。
  3. bmapitrunc 必须成对改 ------ 一个管建、一个管拆。handout 最后一条 hint 就是 "Make sure itrunc frees 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 递归跟随链接且检测环路),是路径名解析机制的完整实战。

相关推荐
GeW1 小时前
RHCE 考试通过率提升:核心考点、实验环境与排错技巧
linux
吴声子夜歌2 小时前
Shell编程实例——与解析相关的任务(二)
linux·运维·shell
mengge.cloud2 小时前
0917Docker 小白实验教程(理论+实操)
linux·服务器·网络·docker
Escalating_xu3 小时前
【内存管理发展史】从 8086 实模式到分页:CPU 如何把虚拟地址变成物理地址
linux·缓存
亿朝亿夕3 小时前
linux服务排查步骤
linux
酸菜。3 小时前
dw_apb_uart总结
linux
倔强的石头1063 小时前
【Linux指南】动静态库系列(八):ELF 加载与进程地址空间:程序还没运行,为什么已经有地址
linux·运维
第十人i3 小时前
Linux 一键重装系统脚本:reinstall 工具详解
linux·服务器
jinyishu_5 小时前
Linux基础指令:从Shell原理到权限
linux