<?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);
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐