现代操作系统=文件系统
【代码】现代操作系统=文件系统。
·
<?php
/**
* ============================================================
* 现代操作系统 · 第四章《文件系统》大白话 + 代码例子
* ============================================================
* 作用:40 个文件系统概念,每条 = 大白话 + 一段 PHP 代码示例。
* 说明:能用真实文件函数(fopen/mkdir/stat...)就用真的,
* 磁盘布局/分配/算法类用 PHP 代码模拟其思想。
* 运行:php filesystem.php 全部
* php filesystem.php 176 看第 176 条(i节点)
* php filesystem.php 链接 关键词搜索
* ============================================================
*/
declare(strict_types=1);
final class Topic
{
public function __construct(
public readonly int $no,
public readonly string $title,
public readonly string $plain,
public readonly string $code
) {}
}
final class FileSystemBook
{
/** @var Topic[] */
private array $topics = [];
public function __construct()
{
$this->load();
}
private function load(): void
{
$data = [
160 => ['文件命名',
'给数据起个名字存盘,名字有规则(扩展名提示类型),让人和系统都能找到它。',
<<<'CODE'
$name = "report.txt"; // 主名 + 扩展名
$ext = pathinfo($name, PATHINFO_EXTENSION); // "txt"
echo "类型提示: $ext\n"; // 扩展名暗示这是文本
CODE],
161 => ['文件结构',
'文件内部怎么组织:可以是一串字节流、定长记录序列、或一棵记录树。',
<<<'CODE'
$byteStream = file_get_contents('a.bin'); // 1) 无结构字节流
$records = str_split($data, 80); // 2) 定长记录(每条80字节)
// 3) 记录树: 按关键字组织, 数据库式(老式大型机用)
CODE],
162 => ['文件类型',
'普通文件(数据)、目录(文件夹)、特殊文件(代表设备)等不同种类。',
<<<'CODE'
$type = filetype('/etc'); // "dir"
echo match($type) {
'file' => '普通文件',
'dir' => '目录',
'char', 'block' => '设备特殊文件',
default => '其他',
};
CODE],
163 => ['文件存取',
'两种读法:顺序存取(从头读到尾)和随机存取(直接跳到任意位置读)。',
<<<'CODE'
$f = fopen('a.txt', 'r');
fread($f, 100); // 顺序: 从头读
fseek($f, 500); // 随机: 直接跳到第500字节
$chunk = fread($f, 100);
fclose($f);
CODE],
164 => ['文件属性',
'名字之外的附加信息:大小、创建/修改时间、权限、所有者等(元数据)。',
<<<'CODE'
$info = stat('a.txt');
echo "大小: {$info['size']} 字节\n";
echo "修改时间: " . date('Y-m-d', $info['mtime']) . "\n";
echo "权限: " . substr(sprintf('%o', $info['mode']), -3) . "\n";
CODE],
165 => ['文件操作',
'系统提供的标准动作:创建、打开、读、写、定位、关闭、删除等。',
<<<'CODE'
$f = fopen('a.txt', 'w'); // 创建/打开
fwrite($f, "hello"); // 写
fseek($f, 0); // 定位
fclose($f); // 关闭
unlink('a.txt'); // 删除
CODE],
166 => ['内存映射文件',
'把文件映射进内存,读写内存就等于读写文件,省去显式 read/write。',
<<<'CODE'
$mmap = str_split(file_get_contents('a.txt'));
$mmap[0] = 'X'; // 改"内存"
file_put_contents('a.txt', implode('', $mmap)); // 等于改了文件
CODE],
167 => ['单层目录系统',
'所有文件平铺在一个目录里,简单但文件一多名字就容易撞车。',
<<<'CODE'
$rootDir = [ // 只有一层, 全平铺
'a.txt' => 'inode1',
'b.txt' => 'inode2',
];
// 文件一多, 起名就容易冲突
CODE],
168 => ['层次目录系统',
'目录里能套目录,形成一棵树,文件分门别类各归各处。',
<<<'CODE'
$tree = [
'home' => [
'user' => [
'doc' => ['a.txt' => 'inode1'],
'pics' => ['p.jpg' => 'inode2'],
],
],
];
CODE],
169 => ['路径名',
'从根开始一级级写出文件位置(绝对路径),或从当前目录算起(相对路径)。',
<<<'CODE'
$absolute = "/home/user/doc/a.txt"; // 绝对: 从根/开始
$relative = "doc/a.txt"; // 相对: 从当前目录算
$parts = explode('/', trim($absolute, '/'));
print_r($parts); // 逐级目录
CODE],
170 => ['目录操作',
'针对文件夹的动作:创建、删除、改名、打开读取里面的条目等。',
<<<'CODE'
mkdir('photos'); // 建目录
$dir = opendir('photos');
while (($entry = readdir($dir)) !== false) echo "$entry\n"; // 读条目
closedir($dir);
rmdir('photos'); // 删目录
CODE],
171 => ['文件系统布局',
'磁盘开头放引导块,接着是超级块(总信息)、空闲管理、i节点区、数据区。',
<<<'CODE'
$diskLayout = [
0 => '引导块(MBR)',
1 => '超级块(文件系统总信息)',
2 => '空闲块管理',
3 => 'i节点区',
4 => '数据块区',
];
CODE],
172 => ['实现文件',
'核心问题:一个文件的数据分散在哪些磁盘块上,怎么记录这些块。',
<<<'CODE'
// 三大记法: 连续 / 链表 / 索引(i节点)
$file = [
'name' => 'a.txt',
'blocks' => [12, 13, 14], // 该文件占用的磁盘块号
];
CODE],
173 => ['连续分配',
'文件占一串连续的块,读得快、实现简单,但删文件后留空洞、难扩容。',
<<<'CODE'
$file = ['start' => 10, 'length' => 5]; // 从第10块起连续5块
$blocks = range($file['start'], $file['start'] + $file['length'] - 1);
print_r($blocks); // [10,11,12,13,14]
CODE],
174 => ['链表分配',
'每块开头存“下一块的块号”,块可分散,但随机访问得顺着链一路找。',
<<<'CODE'
$blocks = [ // 每块指向下一块
10 => ['data'=>'..', 'next'=>23],
23 => ['data'=>'..', 'next'=>7],
7 => ['data'=>'..', 'next'=>-1], // -1 表示结束
];
$b = 10; while ($b != -1) { echo "$b "; $b = $blocks[$b]['next']; }
CODE],
175 => ['采用内存表的链表分配',
'把所有“下一块”指针抽出来集中放进内存表(FAT),随机访问就快了。',
<<<'CODE'
$FAT = [10=>23, 23=>7, 7=>-1]; // 文件分配表, 整张放内存
$b = 10;
while ($b != -1) { echo "$b "; $b = $FAT[$b]; } // 在内存里跟链, 快
CODE],
176 => ['i 节点',
'每个文件一个 i节点,集中存它的属性和数据块地址,打开文件才需载入。',
<<<'CODE'
$inode = [
'mode' => 0644,
'size' => 5120,
'owner' => 'user',
'direct' => [12, 13, 14, 15], // 直接块地址
'single' => 88, // 一级间接块(指向块号表)
];
CODE],
177 => ['目录的实现',
'目录本身也是文件,里面每条记录把“文件名”对应到它的 i节点号。',
<<<'CODE'
$directory = [ // 目录 = 文件名 -> inode号
'a.txt' => 1,
'b.txt' => 2,
'sub' => 3,
];
$inodeNo = $directory['a.txt']; // 查名字得到inode号
CODE],
178 => ['共享文件',
'一个文件出现在多个目录里(共享),结构从树变成有向无环图(DAG)。',
<<<'CODE'
$inode7 = ['size'=>100, 'links'=>2]; // 同一文件
$dirA = ['report' => 7]; // A目录指向它
$dirB = ['月报' => 7]; // B目录也指向它 -> 共享
CODE],
179 => ['硬链接与符号链接',
'硬链接=多个名字指向同一 i节点;符号链接=一个存“目标路径”的小文件。',
<<<'CODE'
// 硬链接: 共享同一inode, 计数+1
link('a.txt', 'a_hard.txt'); // 同一inode两个名字
// 符号链接: 存一段路径, 像快捷方式
symlink('a.txt', 'a_soft.txt'); // a_soft内容="a.txt"
CODE],
180 => ['日志结构文件系统',
'把整块磁盘当一个大日志,所有写入都追加到末尾,写性能极高。',
<<<'CODE'
$log = []; // 整盘是一条大日志
function writeLFS(&$log, $data) {
$log[] = $data; // 一切写入都追加到末尾(顺序写, 快)
return count($log) - 1; // 返回位置
}
CODE],
181 => ['日志文件系统',
'改动前先把“打算做什么”记进日志,崩溃后照日志重做,保证不烂尾(如 ext4)。',
<<<'CODE'
$journal = [];
function transaction(&$journal, $ops) {
$journal[] = ['intent' => $ops]; // 1.先记意图到日志
foreach ($ops as $op) apply($op); // 2.再真正执行
$journal[] = ['commit' => true]; // 3.提交; 崩溃可按日志重做
}
CODE],
182 => ['虚拟文件系统 VFS',
'在各种真实文件系统(ext4/NTFS/NFS)上加一层统一接口,应用一套API通吃。',
<<<'CODE'
interface VFS { // 统一接口
public function open(string $path);
public function read($handle): string;
}
class Ext4 implements VFS { /* ... */ }
class NTFS implements VFS { /* ... */ }
// 应用只管调 VFS, 不管底下是哪种文件系统
CODE],
183 => ['磁盘空间管理',
'怎么把磁盘空间切块分给文件:定大小的块,再记录哪些块空着。',
<<<'CODE'
define('BLOCK', 4096); // 块大小 4KB
function blocksNeeded($fileSize) {
return (int) ceil($fileSize / BLOCK); // 文件要占几块(向上取整)
}
echo blocksNeeded(5000); // 2 块
CODE],
184 => ['块大小',
'块大读得快但小文件浪费多(内部碎片),块小省空间但慢,要权衡。',
<<<'CODE'
function waste($fileSize, $block) {
$used = ceil($fileSize / $block) * $block;
return $used - $fileSize; // 浪费的空间(内部碎片)
}
echo waste(100, 4096); // 3996 字节被浪费!
CODE],
185 => ['记录空闲块',
'两种记法:空闲块号链表,或用位图(每块 1 位标记空/占)。',
<<<'CODE'
$freeList = [3, 7, 8, 15]; // 1) 空闲块号链表
$bitmap = [1,1,1,0,1,0,0]; // 2) 位图: 0=空闲
CODE],
186 => ['磁盘配额',
'给每个用户设上限,用得太多就警告或禁止再写,防止一个人占满磁盘。',
<<<'CODE'
$quota = ['user' => ['limit'=>1000, 'used'=>980]];
function checkQuota(&$quota, $user, $add) {
if ($quota[$user]['used'] + $add > $quota[$user]['limit'])
die("超出配额!");
$quota[$user]['used'] += $add;
}
CODE],
187 => ['文件系统备份',
'定期把数据复制一份留底,分全量备份和只备改动的增量备份。',
<<<'CODE'
function backup($files, $lastBackupTime, $mode) {
foreach ($files as $f => $mtime)
if ($mode == 'full' || $mtime > $lastBackupTime)
copyToBackup($f); // 全量: 全拷; 增量: 只拷改过的
}
CODE],
188 => ['物理转储与逻辑转储',
'物理转储=逐块照搬整盘;逻辑转储=按文件/目录有选择地备份改动。',
<<<'CODE'
// 物理: 不管内容, 块号0到N原样复制
for ($block = 0; $block < $totalBlocks; $block++) copyBlock($block);
// 逻辑: 从某目录起, 只挑改过的文件
function logicalDump($dir, $since) { /* 遍历, 备份 mtime>since 的 */ }
CODE],
189 => ['文件系统的一致性',
'崩溃可能让块/i节点对不上,开机用 fsck 扫描修复(数每块被引用几次)。',
<<<'CODE'
function fsck($blocks, $inodes) {
$count = array_fill_keys(array_keys($blocks), 0);
foreach ($inodes as $i) foreach ($i['blocks'] as $b) $count[$b]++;
foreach ($count as $b => $c)
if ($c > 1) echo "块$b 被引用$c 次, 不一致!\n";
}
CODE],
190 => ['文件系统性能',
'磁盘比内存慢百万倍,靠缓存、预读、就近摆放等手段来提速。',
<<<'CODE'
$tricks = [
'高速缓存' => '常用块留内存',
'块提前读' => '顺序访问时预读下一块',
'减少臂动' => 'i节点与数据块就近放',
];
CODE],
191 => ['高速缓存',
'把常用磁盘块留在内存里,再读直接命中,不用真去转磁盘。',
<<<'CODE'
$cache = []; // 块缓存
function readBlock($n, &$cache, $disk) {
return $cache[$n] ?? ($cache[$n] = $disk[$n]); // 命中即返回, 否则读盘缓存
}
CODE],
192 => ['块提前读',
'发现你在顺序读,就提前把下一块也读进内存,等你要时已就绪。',
<<<'CODE'
function readAhead($currentBlock, &$cache, $disk) {
$next = $currentBlock + 1;
if (!isset($cache[$next]))
$cache[$next] = $disk[$next]; // 顺序访问时, 预读下一块
}
CODE],
193 => ['减少磁盘臂运动',
'把经常一起用的块(如i节点和它的数据)放得近一些,减少磁头来回跑。',
<<<'CODE'
// 把 i节点 和它的数据块放在同一柱面组, 磁头少跑
$cylinderGroup = [
'inodes' => [1, 2, 3],
'data' => [10, 11, 12], // 就在旁边
];
CODE],
194 => ['磁盘碎片整理',
'把零散的文件块重新挪到一起连续存放,让读取更快(机械盘有用,SSD不需要)。',
<<<'CODE'
function defrag($fileBlocks) { // 把分散块挪成连续
$target = 0; $map = [];
foreach ($fileBlocks as $b) $map[$b] = $target++; // 重排到连续位置
return $map; // 旧块号 -> 新连续块号
}
CODE],
195 => ['日志文件系统的实现',
'把多个改动打包成一个事务,先写日志再落盘,提交后清日志,崩溃能回放。',
<<<'CODE'
function commitTxn(&$journal, $ops) {
$txn = ['begin', ...$ops, 'commit'];
foreach ($txn as $rec) $journal[] = $rec; // 整个事务写日志
flushToDisk($ops); // 落盘
// 崩溃时: 有commit的重做, 没commit的丢弃
}
CODE],
196 => ['闪存文件系统',
'闪存(SSD/U盘)不能原地改、且每块擦写有寿命,需磨损均衡+垃圾回收。',
<<<'CODE'
$wearCount = []; // 记录每块擦写次数
function pickBlock($wearCount) {
return array_keys($wearCount, min($wearCount))[0]; // 挑磨损最少的写
} // 磨损均衡, 延长寿命
CODE],
197 => ['ISO 9660 文件系统',
'光盘(CD/DVD)的标准格式,只读、结构简单、跨系统通用。',
<<<'CODE'
$iso9660 = [
'type' => '只读',
'maxDepth' => 8, // 目录最多8层(原始规范)
'naming' => '8.3 / Joliet扩展',
'sectorSize'=> 2048,
];
CODE],
198 => ['UNIX V7 文件系统',
'经典 Unix 文件系统,奠定了 i节点 + 多级目录 + 间接块的基本设计。',
<<<'CODE'
$v7Inode = [
'direct' => [/* 10个直接块 */],
'singleIndirect' => 1, // 一级间接
'doubleIndirect' => 1, // 二级间接
'tripleIndirect' => 1, // 三级间接 -> 撑起超大文件
];
CODE],
199 => ['CD-ROM 文件系统',
'专为光盘设计的只读文件系统(ISO 9660 及其扩展),按螺旋轨道顺序存。',
<<<'CODE'
// 光盘数据沿一条螺旋轨道顺序排列
$cdrom = [
'track' => 'single spiral', // 单条螺旋
'format' => 'ISO 9660',
'writable'=> false, // 只读
];
CODE],
];
foreach ($data as $no => [$title, $plain, $code]) {
$this->topics[$no] = new Topic($no, $title, $plain, $code);
}
}
public function renderAll(): void
{
$this->printHeader('现代操作系统 · 第四章《文件系统》大白话 + 代码例子');
foreach ($this->topics as $t) $this->printTopic($t);
$this->printFooter(count($this->topics));
}
public function renderOne(int $no): void
{
if (!isset($this->topics[$no])) {
echo "⚠️ 没有第 {$no} 条(本章 160~199)\n";
return;
}
$this->printHeader("第 {$no} 条");
$this->printTopic($this->topics[$no]);
}
public function search(string $kw): void
{
$hit = array_filter(
$this->topics,
fn(Topic $t) => mb_strpos($t->title, $kw) !== false
|| mb_strpos($t->plain, $kw) !== false
|| mb_strpos($t->code, $kw) !== false
);
if (!$hit) { echo "🔍 没找到包含“{$kw}”的条目\n"; return; }
$this->printHeader("搜索“{$kw}”,命中 " . count($hit) . " 条");
foreach ($hit as $t) $this->printTopic($t);
}
private function printHeader(string $title): void
{
echo "\n" . str_repeat('=', 62) . "\n {$title}\n" . str_repeat('=', 62) . "\n";
}
private function printTopic(Topic $t): void
{
printf("\n【%03d】%s\n", $t->no, $t->title);
echo " ▷ 大白话:" . wordwrap_cn($t->plain, 36, "\n ") . "\n";
echo " ▶ 代码例子:\n";
foreach (explode("\n", $t->code) as $line) {
echo " | " . $line . "\n";
}
}
private function printFooter(int $count): void
{
echo "\n" . str_repeat('-', 62) . "\n";
echo " 共 {$count} 条,每条含“大白话 + PHP代码例子”。\n";
echo str_repeat('-', 62) . "\n";
}
}
/** 中文换行 */
function wordwrap_cn(string $text, int $width, string $break): string
{
$len = mb_strlen($text);
if ($len <= $width) return $text;
$out = '';
for ($i = 0; $i < $len; $i += $width) {
$out .= mb_substr($text, $i, $width);
if ($i + $width < $len) $out .= $break;
}
return $out;
}
// ===================== 入口 =====================
$book = new FileSystemBook();
$arg = $argv[1] ?? null;
if ($arg === null) $book->renderAll();
elseif (ctype_digit($arg)) $book->renderOne((int) $arg);
else $book->search($arg);
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)