现代操作系统=、输入/输出
·
<?php
/**
* ============================================================
* 现代操作系统 · 第五章《输入/输出》大白话 + 代码例子
* ============================================================
* 作用:40 个 I/O 概念,每条 = 大白话 + 一段 PHP 代码示例。
* 说明:I/O 多为硬件机制与算法,几乎全部用 PHP 代码模拟其思想。
* 运行:php io.php 全部
* php io.php 221 看第 221 条(电梯算法)
* php io.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 IoBook
{
/** @var Topic[] */
private array $topics = [];
public function __construct()
{
$this->load();
}
private function load(): void
{
$data = [
200 => ['I/O 设备的分类',
'按怎么传数据分两大类:块设备(成块读写)和字符设备(一个字节一个字节来)。',
<<<'CODE'
$devices = [
'disk' => 'block', // 块设备
'keyboard' => 'char', // 字符设备
'printer' => 'char',
];
CODE],
201 => ['块设备与字符设备',
'块设备能按地址随机读某一块(如硬盘);字符设备只能顺序收发字节流(如键盘)。',
<<<'CODE'
// 块设备: 可寻址, 读第 n 块
function readBlock($dev, $n) { return $dev->blocks[$n]; }
// 字符设备: 只能一个个流式收
function readChar($stream) { return fgetc($stream); }
CODE],
202 => ['设备控制器',
'硬件里的小电路板,CPU 给它下命令,它去驱动具体设备并报告结果。',
<<<'CODE'
$controller = [
'command' => null, // CPU 写入命令寄存器
'status' => 'idle', // 设备状态寄存器
'data' => null, // 数据寄存器
];
$controller['command'] = 'READ'; // CPU 下令
CODE],
203 => ['内存映射 I/O',
'把设备的寄存器映射成内存地址,CPU 像读写内存一样操作设备,无需特殊指令。',
<<<'CODE'
// 设备寄存器被映射到某内存地址
$REG_ADDR = 0xFE000;
function deviceWrite(&$mem, $addr, $val) {
$mem[$addr] = $val; // 写"内存"其实是写设备寄存器
}
deviceWrite($mem, $REG_ADDR, 'START');
CODE],
204 => ['直接存储器存取 DMA',
'让专门的 DMA 控制器替 CPU 在设备和内存间搬数据,搬完才通知 CPU,省得 CPU 盯着。',
<<<'CODE'
function dmaTransfer($device, &$mem, $start, $count) {
for ($i = 0; $i < $count; $i++)
$mem[$start + $i] = $device->read(); // DMA 自己搬, CPU 去干别的
interrupt('DMA_DONE'); // 搬完才打扰 CPU
}
CODE],
205 => ['重温中断',
'设备干完活,给 CPU 发个信号(中断),CPU 暂停手头事去处理,处理完再回来。',
<<<'CODE'
function onInterrupt($irq, $handlers) {
$saved = saveContext(); // 1.保存现场
$handlers[$irq](); // 2.执行中断处理程序
restoreContext($saved); // 3.恢复现场, 继续原任务
}
CODE],
206 => ['精确中断与不精确中断',
'精确中断=停在干净的指令边界,现场清晰好恢复;不精确=停得乱,难善后。',
<<<'CODE'
$interrupt = [
'pc' => 0x400C, // 精确: 明确停在哪条指令
'precise' => true, // 之前的全做完, 之后的全没做
];
// 不精确: 有的指令做一半, 现场混乱, 恢复困难
CODE],
207 => ['I/O 软件的目标',
'追求设备无关性(同一套代码操作不同设备)、统一命名、错误处理、同步异步透明等。',
<<<'CODE'
$goals = [
'设备无关性' => '同一份代码读硬盘或U盘',
'统一命名' => '用路径名访问任何设备',
'错误处理' => '尽量在底层就地解决',
'缓冲' => '速度不匹配靠缓冲区调和',
];
CODE],
208 => ['程序控制 I/O',
'CPU 亲自一个字节一个字节地传,传一个就反复查设备好了没(忙等待),最浪费 CPU。',
<<<'CODE'
function programmedIO($data, $device) {
foreach (str_split($data) as $ch) {
while ($device->status != 'ready') { /* 忙等, 空转查询 */ }
$device->write($ch); // CPU 亲自喂一个字节
}
}
CODE],
209 => ['中断驱动 I/O',
'CPU 喂一个字节后就去干别的,设备好了发中断再喂下一个,不再空转。',
<<<'CODE'
function startIO($device, $ch) {
$device->write($ch); // 喂一个就走人
// CPU 去干别的事...
}
function onReady($device, $next) { // 设备就绪的中断处理
if ($next !== null) startIO($device, $next); // 再喂下一个
}
CODE],
210 => ['使用 DMA 的 I/O',
'把整批传输交给 DMA,CPU 全程不管字节,只在整批搬完时被中断一次。',
<<<'CODE'
function dmaIO($device, &$mem, $buf, $count) {
setupDMA($device, $buf, $count); // 告诉DMA: 搬count字节到buf
// CPU 完全不参与搬运...
// 整批完成才来一次中断:
onInterrupt('DMA_DONE', fn() => echo "整批传完\n");
}
CODE],
211 => ['中断处理程序',
'响应中断的那段代码:保存现场→处理设备→唤醒等待的进程→恢复现场。',
<<<'CODE'
function interruptHandler($device) {
$ctx = saveContext(); // 保存现场
ackInterrupt($device); // 应答设备
$device->buffer[] = $device->read();
wakeup($device->waitingProcess); // 唤醒等数据的进程
restoreContext($ctx); // 恢复现场
}
CODE],
212 => ['设备驱动程序',
'专为某型号设备写的代码,懂它的寄存器和脾气,把上层通用请求翻译成具体命令。',
<<<'CODE'
interface Driver {
public function read(int $block): string;
public function write(int $block, string $data): void;
}
class DiskDriver implements Driver { // 每种设备一个驱动
public function read(int $block): string { /* 操作该硬盘寄存器 */ return ''; }
public function write(int $block, string $data): void { /* ... */ }
}
CODE],
213 => ['与设备无关的 I/O 软件',
'内核里通用的那层:统一命名、缓冲、分配释放、块大小统一,不管底下是什么设备。',
<<<'CODE'
function readFile($path) { // 上层不关心是硬盘还是U盘
$driver = lookupDriver($path); // 按路径找到对应驱动
return $driver->read(0); // 统一接口调用
}
CODE],
214 => ['缓冲',
'设备和 CPU 速度差太多,中间放个缓冲区先攒着,攒够或合适再一起处理。',
<<<'CODE'
$buffer = '';
function bufferedWrite(&$buffer, $ch, $device) {
$buffer .= $ch;
if (strlen($buffer) >= 512) { // 攒满一块才真写
$device->write($buffer);
$buffer = '';
}
}
CODE],
215 => ['用户空间的 I/O 软件',
'一部分 I/O 工作在用户态完成,如库函数(printf)、假脱机(spooling)打印守护。',
<<<'CODE'
// printf 这类库函数在用户态先格式化, 再调系统调用
$formatted = sprintf("结果=%d\n", 42);
fwrite(STDOUT, $formatted); // 格式化在用户空间, 输出才进内核
// 打印任务先入 spool 目录, 由守护进程慢慢打
CODE],
216 => ['磁盘硬件',
'盘片分柱面、磁道、扇区,磁头要移动到对的位置才能读,机械动作很慢。',
<<<'CODE'
$geometry = [
'cylinders' => 16383,
'heads' => 16,
'sectors' => 63,
];
$capacity = 16383 * 16 * 63 * 512; // 柱面×磁头×扇区×512字节
CODE],
217 => ['磁盘格式化',
'低级格式化划出扇区和校验,高级格式化建文件系统(超级块、空闲表、根目录)。',
<<<'CODE'
function format($disk) {
foreach ($disk->tracks as &$t)
$t = ['preamble'=>1, 'data'=>'', 'ecc'=>0]; // 低级: 划扇区+校验
initFileSystem($disk); // 高级: 建超级块/空闲表/根目录
}
CODE],
218 => ['磁盘臂调度算法',
'一堆读写请求等着,怎么安排磁头移动顺序最省时间,就是臂调度。',
<<<'CODE'
$requests = [98, 183, 37, 122, 14, 124, 65, 67]; // 待处理磁道号
$head = 53; // 磁头当前位置
// 用 FCFS / SSTF / 电梯 等算法决定先后顺序
CODE],
219 => ['先来先服务调度(FCFS)',
'按请求到达顺序处理磁道,公平但磁头可能来回乱跑,总移动距离大。',
<<<'CODE'
function fcfs($requests, $head) {
$move = 0;
foreach ($requests as $r) { $move += abs($r - $head); $head = $r; }
return $move; // 总移动距离(可能很大)
}
CODE],
220 => ['最短寻道优先(SSTF)',
'每次都挑离磁头最近的请求先做,总移动小,但远处请求可能被一直插队饿死。',
<<<'CODE'
function sstf($requests, $head) {
$move = 0;
while ($requests) {
usort($requests, fn($a,$b)=>abs($a-$head)-abs($b-$head));
$next = array_shift($requests); // 选最近的
$move += abs($next - $head); $head = $next;
}
return $move;
}
CODE],
221 => ['电梯算法(SCAN)',
'磁头像电梯一样朝一个方向走到头再回头,沿途顺路处理请求,公平又高效。',
<<<'CODE'
function elevator($requests, $head, $dir = 'up') {
sort($requests);
$up = array_filter($requests, fn($r) => $r >= $head);
$down = array_reverse(array_filter($requests, fn($r) => $r < $head));
return $dir == 'up' ? [...$up, ...$down] : [...$down, ...array_reverse($up)];
} // 先一路向上, 到头再向下
CODE],
222 => ['错误处理',
'坏扇区、读写失败等错误,先重试,不行就用备用扇区替换或上报。',
<<<'CODE'
function readWithRetry($disk, $sector, $maxRetry = 3) {
for ($i = 0; $i < $maxRetry; $i++) {
$data = $disk->read($sector);
if ($data !== false) return $data; // 成功
}
return $disk->remapSpare($sector); // 多次失败 -> 用备用扇区替换
}
CODE],
223 => ['稳定存储器',
'用两块盘互为备份+小心写入顺序,保证崩溃时数据要么是旧的、要么是新的,绝不损坏。',
<<<'CODE'
function stableWrite(&$disk1, &$disk2, $addr, $data) {
$disk1[$addr] = $data; verify($disk1[$addr]); // 先写并校验盘1
$disk2[$addr] = $data; verify($disk2[$addr]); // 再写并校验盘2
} // 崩在中间也能从另一块恢复
CODE],
224 => ['RAID 磁盘阵列',
'把多块盘当一块用:条带化提速、镜像或校验防坏盘,兼顾性能和可靠。',
<<<'CODE'
$raid = [
'RAID0' => '条带化, 快, 不冗余',
'RAID1' => '镜像, 两份相同数据',
'RAID5' => '分布式校验, 坏一块能恢复',
];
function raid0Write($disks, $i, $data) {
$disks[$i % count($disks)][] = $data; // 数据分散到各盘(条带)
}
CODE],
225 => ['固态硬盘 SSD',
'用闪存芯片存数据,无机械臂、随机读极快,但写有寿命,需磨损均衡。',
<<<'CODE'
$ssd = ['eraseCount' => []]; // 记录每块擦写次数
function ssdWrite(&$ssd, $data) {
$block = array_keys($ssd['eraseCount'],
min($ssd['eraseCount']))[0]; // 挑磨损最少的
$ssd['eraseCount'][$block]++; // 磨损均衡
}
CODE],
226 => ['时钟硬件',
'一个晶振按固定频率产生脉冲,硬件计数到点就发一次时钟中断。',
<<<'CODE'
$clock = ['freq' => 1000, 'counter' => 0]; // 1000Hz, 每毫秒一跳
function tick(&$clock) {
$clock['counter']++;
interrupt('TIMER'); // 每跳触发时钟中断
}
CODE],
227 => ['时钟软件',
'时钟中断到来时,系统更新时间、扣减进程时间片、检查定时器、统计 CPU 占用。',
<<<'CODE'
function clockISR(&$current, &$timers) {
updateTimeOfDay(); // 更新墙上时间
if (--$current['quantum'] <= 0) // 时间片用完
schedule(); // 触发调度
foreach ($timers as &$t) if (--$t['ticks'] == 0) $t['cb'](); // 定时器到点
}
CODE],
228 => ['软定时器',
'内核维护一串到期时间,到点就执行回调;很多定时器共用一个硬件时钟。',
<<<'CODE'
$timers = [];
function setTimer(&$timers, $ms, $callback) {
$timers[] = ['expire' => now() + $ms, 'cb' => $callback];
usort($timers, fn($a,$b)=>$a['expire']-$b['expire']); // 按到期排序
}
CODE],
229 => ['字符终端',
'老式终端靠串口一个字符一个字符地收发,处理回显、退格等行编辑。',
<<<'CODE'
function terminalInput($ch, &$line) {
if ($ch == "\x7f") array_pop($line); // 退格: 删最后一个
elseif ($ch == "\n") return implode('', $line); // 回车: 提交整行
else { $line[] = $ch; echo $ch; } // 普通字符: 存入并回显
return null;
}
CODE],
230 => ['输入软件',
'管键盘输入两种模式:原始模式(逐字符直接给程序)和规范模式(攒成一行再给)。',
<<<'CODE'
$mode = 'canonical'; // 规范模式: 攒整行
function readInput($ch, &$buf, $mode) {
if ($mode == 'raw') return $ch; // 原始模式: 来一个给一个
$buf[] = $ch; // 规范模式: 攒着
return ($ch == "\n") ? implode('', $buf) : null; // 回车才整行返回
}
CODE],
231 => ['输出软件',
'把要显示的内容处理转义序列(如移动光标、变色)后送到屏幕。',
<<<'CODE'
function moveCursor($row, $col) {
echo "\033[{$row};{$col}H"; // ANSI转义: 移动光标
}
function setColor($c) { echo "\033[{$c}m"; } // 变色
echo setColor(31), "红字", "\033[0m\n";
CODE],
232 => ['图形用户界面 GUI',
'用窗口、图标、菜单、鼠标(WIMP)交互,靠事件循环响应点击和键盘。',
<<<'CODE'
$eventQueue = [];
while ($event = array_shift($eventQueue)) { // GUI 事件循环
match ($event['type']) {
'click' => onClick($event),
'keypress' => onKey($event),
'paint' => redrawWindow($event),
default => null,
};
}
CODE],
233 => ['键盘、鼠标与显示器',
'键盘发扫描码、鼠标发位移和按键、显示器靠显存里的像素刷新画面。',
<<<'CODE'
$keyboard = ['scancode' => 0x1E]; // 'A' 的扫描码
$mouse = ['dx' => 5, 'dy' => -3, 'buttons' => 0b001]; // 位移+按键
$framebuffer = []; // 显存: 每个像素的颜色
$framebuffer[0] = 0xFF0000; // 左上角设为红色
CODE],
234 => ['X 窗口系统',
'Unix 图形的客户端/服务器架构:X服务器管屏幕硬件,程序当客户端发绘图请求。',
<<<'CODE'
// 程序(客户端)向 X 服务器发绘图请求, 可跨网络
$xClient->send(['op'=>'DrawLine', 'from'=>[0,0], 'to'=>[100,100]]);
$xClient->send(['op'=>'DrawText', 'pos'=>[10,10], 'text'=>'Hi']);
// X 服务器收到后真正画到屏幕上
CODE],
235 => ['瘦客户机',
'本地只负责显示和输入,计算和数据都在远端服务器,客户端做得很轻。',
<<<'CODE'
// 瘦客户机: 只发输入、收画面
$thinClient = [
'send' => fn($input) => $server->process($input), // 上传按键鼠标
'recv' => fn() => $server->getScreen(), // 下载屏幕图像
];
CODE],
236 => ['电源管理',
'没事就让设备/CPU 进低功耗状态,需要时再唤醒,延长电池、省电。',
<<<'CODE'
function powerManage(&$device, $idleTime) {
if ($idleTime > 5) $device['state'] = 'sleep'; // 闲置进睡眠
if ($idleTime > 30) $device['state'] = 'off'; // 更久就关掉
}
// 有请求时再 wakeup
CODE],
237 => ['电源管理:硬件问题',
'硬件先天要省电:屏幕、硬盘、CPU、内存各自支持分级降频/关断。',
<<<'CODE'
$hardwarePower = [
'cpu' => ['states' => ['C0运行','C1停','C3深睡']], // CPU 多档
'screen' => ['dim' => true, 'off' => true], // 屏幕调暗/关
'disk' => ['spinDown' => true], // 硬盘停转
];
CODE],
238 => ['电源管理:操作系统问题',
'系统决定何时关谁、降到几档,在省电和性能/响应之间权衡。',
<<<'CODE'
function osPowerPolicy($load) {
return match (true) {
$load > 80 => 'performance', // 高负载: 全速
$load > 20 => 'balanced', // 中等: 平衡
default => 'powersave', // 空闲: 省电降频
};
}
CODE],
239 => ['电源管理:应用程序问题',
'应用也该配合省电:批量处理、减少唤醒、空闲时少轮询,别老把CPU闹醒。',
<<<'CODE'
// 坏: 频繁轮询, 老把CPU闹醒
// while (true) { check(); usleep(1000); }
// 好: 批量+长睡, 配合省电
function batchWork($jobs) {
foreach ($jobs as $j) process($j); // 一次性处理一批
sleep(60); // 然后长睡, 不频繁唤醒
}
CODE],
];
foreach ($data as $no => [$title, $plain, $code]) {
$this->topics[(int) $no] = new Topic((int) $no, $title, $plain, $code);
}
ksort($this->topics);
}
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} 条(本章 200~239)\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 IoBook();
$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)