<?php
/**
 * ============================================================
 *  现代操作系统 · 第二章《进程与线程》大白话 + 代码例子
 * ============================================================
 *  作用:56 个进程/线程概念,每条 = 大白话 + 一段 PHP 代码示例。
 *  说明:能用真实扩展的用真函数(pcntl/sem/msg),
 *        纯算法/硬件概念用 PHP 代码模拟,重在演示思想。
 *  运行:php proc_thread.php            全部
 *        php proc_thread.php 78         看第 78 条
 *        php proc_thread.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 ProcThreadBook
{
    /** @var Topic[] */
    private array $topics = [];

    public function __construct()
    {
        $this->load();
    }

    private function load(): void
    {
        $data = [
            50 => ['进程模型',
                '一个跑起来的程序就是一个进程,CPU飞快地在多个进程间切换,造成“同时运行”的错觉。',
                <<<'CODE'
$pid = pcntl_fork();           // 复制出一个进程
if ($pid == 0) echo "我是子进程\n";
else            echo "我是父进程, 子pid=$pid\n";
CODE],

            51 => ['进程的创建',
                'fork 的本质 = 申请内存 + 复制代码 + 排进就绪队列。',
                <<<'CODE'
$pid = pcntl_fork();
if ($pid == 0) { echo "新进程诞生\n"; exit; }
pcntl_wait($s);                // 父进程等它
CODE],

            52 => ['进程的终止',
                '四种死法:正常退出、出错退出、崩溃被杀、被别人 kill。',
                <<<'CODE'
$pid = pcntl_fork();
if ($pid == 0) exit(0);        // 子进程自愿退出
posix_kill($pid, SIGKILL);     // 或被父进程强杀
CODE],

            53 => ['进程的层次结构',
                '父进程 fork 出子进程,形成家族树;子进程能查到自己的父亲。',
                <<<'CODE'
if (pcntl_fork() == 0) {
    echo "我爸pid=" . posix_getppid() . "\n";
    exit;
}
echo "我是父进程 " . getmypid() . "\n";
CODE],

            54 => ['进程的状态',
                '三态:运行(占CPU)、就绪(等CPU)、阻塞(等事件),之间不停切换。',
                <<<'CODE'
$state = 'ready';              // 就绪
$state = 'running';            // 拿到CPU
$state = 'blocked';            // 去读硬盘, 让出CPU
$state = 'ready';             // 数据好了, 重新排队
CODE],

            55 => ['进程的实现',
                '靠一张“进程表”管理;切换 = 保存当前现场 + 载入下一个现场。',
                <<<'CODE'
$table = [
  1 => ['state'=>'running', 'pc'=>100],
  2 => ['state'=>'ready',   'pc'=>0],
];
$table[1]['pc'] = $cpuPC;     // 切走前保存
$cpuPC = $table[2]['pc'];     // 切入时载入
CODE],

            56 => ['进程控制块(PCB)',
                '进程的“户口本”:进程号、状态、程序计数器、寄存器、打开的文件。',
                <<<'CODE'
$pcb = [
  'pid'   => 1234,
  'state' => 'running',
  'pc'    => 0x400,           // 跑到哪一行
  'regs'  => [],              // 寄存器现场
  'files' => ['a.txt'],
];
CODE],

            57 => ['多道程序设计模型',
                '进程越多 CPU 利用率越高,可用概率公式估算。',
                <<<'CODE'
$p = 0.8; $n = 5;             // 5进程, 每个80%在等IO
$cpuUsage = 1 - pow($p, $n);  // 利用率公式
echo round($cpuUsage * 100) . "%\n";  // ≈ 67%
CODE],

            58 => ['线程的使用',
                '一个进程里开多个线程,共享内存,并行干不同的事(需 parallel 扩展)。',
                <<<'CODE'
$r1 = new parallel\Runtime();
$f1 = $r1->run(fn() => "收数据");
$f2 = (new parallel\Runtime())->run(fn() => "画界面");
echo $f1->value() . "," . $f2->value() . "\n";
CODE],

            59 => ['经典线程模型',
                '进程=资源盒子(共享),线程=执行体(私有栈和寄存器)。',
                <<<'CODE'
$process = ['memory'=>[], 'files'=>[]];   // 共享
$thread1 = ['stack'=>[], 'regs'=>[]];     // 私有
$thread2 = ['stack'=>[], 'regs'=>[]];     // 私有
CODE],

            60 => ['POSIX 线程(Pthreads)',
                '标准线程接口:create / join / exit,跨 Unix 通用。',
                <<<'CODE'
class Worker extends Thread {          // pthreads 扩展
    public function run() { echo "线程在跑\n"; }
}
$t = new Worker();
$t->start();                           // 创建
$t->join();                            // 等待结束
CODE],

            61 => ['在用户空间实现线程',
                '程序自己用协程切换,系统不知情;快,但一个卡住全卡住。',
                <<<'CODE'
function task($n){ for($i=0;$i<2;$i++){ echo "$n$i "; yield; } }
$a = task('A'); $b = task('B');
while ($a->valid() || $b->valid()) {   // 自己当调度器
    $a->next(); $b->next();
}
CODE],

            62 => ['在内核中实现线程',
                '内核管理线程;一个阻塞了,内核能切到同进程别的线程。',
                <<<'CODE'
$rt = new parallel\Runtime();          // parallel = 内核级线程
$future = $rt->run(function(){
    sleep(1); return 1;                // 它阻塞不连累主线程
});
echo "主线程继续干活\n";
echo $future->value();
CODE],

            63 => ['混合实现',
                'M 个用户级协程映射到 N 个内核线程上(类似 Go 的 goroutine)。',
                <<<'CODE'
$pool = [];                            // N 个内核线程
for ($i = 0; $i < 4; $i++) $pool[] = new parallel\Runtime();
// 成千上万协程轮流丢进这 4 个线程跑
foreach ($coroutines as $i => $c) $pool[$i % 4]->run($c);
CODE],

            64 => ['调度程序激活机制',
                '线程将阻塞前,内核“上行调用”通知用户层赶紧切别的线程。',
                <<<'CODE'
function onAboutToBlock($scheduler) {
    // 内核回调用户层: "我要睡了, 你切别人"
    $scheduler->switchToNext();
}
CODE],

            65 => ['弹出式线程',
                '消息一来立刻 new 一个线程处理,处理完销毁。',
                <<<'CODE'
while ($req = accept()) {
    (new parallel\Runtime())->run(      // 即来即建
        fn() => handle($req)
    );
}
CODE],

            66 => ['使单线程代码多线程化',
                '坑:全局变量被多线程抢着改。解法:局部变量 / 加锁。',
                <<<'CODE'
$GLOBALS['errno'] = 0;        // 共享, 多线程会互相覆盖
// 线程A刚设errno=2, 线程B又设errno=5 -> A拿到错的
// 解法: 改用线程局部变量, 或访问前加锁
CODE],

            67 => ['进程间通信(IPC)',
                '进程内存隔离,靠系统牵线交换数据,如管道。',
                <<<'CODE'
$pair = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
if (pcntl_fork() == 0) { fwrite($pair[0], "hi爸"); exit; }
echo fread($pair[1], 100);   // 子进程发, 父进程收
CODE],

            68 => ['竞争条件',
                '两进程同时读改写共享数据,结果看运气,时对时错。',
                <<<'CODE'
$balance = 100;              // 共享余额
$a = $balance;              // 进程A读到100
$b = $balance;              // 进程B也读到100
$balance = $a - 50;         // A写回 50
$balance = $b - 50;         // B又写回 50 (本该是0!丢了一次扣款)
CODE],

            69 => ['临界区',
                '访问共享数据的代码段,同一时刻只准一个进程进入。',
                <<<'CODE'
sem_acquire($mutex);         // 进入临界区
$shared++;                   // <- 临界区, 独占
sem_release($mutex);         // 离开临界区
CODE],

            70 => ['忙等待与互斥',
                '最笨的互斥:进不去就空转反复查,白耗 CPU。',
                <<<'CODE'
while ($lock == 1) {
    // 啥也不干, 空转, 白白烧CPU (忙等待)
}
$lock = 1;                   // 终于进去了
CODE],

            71 => ['屏蔽中断',
                '进临界区前关中断,CPU 不被切走;危险且对多核无效,只内核偶用。',
                <<<'CODE'
cli();                       // 关中断(内核伪指令)
$shared++;                   // 没人能打断
sti();                       // 开中断
CODE],

            72 => ['锁变量',
                '“看”和“改”之间有空隙,两进程可能都看到 0 而同时进入。',
                <<<'CODE'
if ($lock == 0) {            // A、B 可能同时看到 0
    $lock = 1;               // 都改成1, 都进去了 -> 失败!
    critical();
}
CODE],

            73 => ['严格轮换法',
                '用 turn 变量强制一人一次轮流,能互斥但太死板。',
                <<<'CODE'
while ($turn != $me) {       // 不到我就死等
    /* spin */
}
critical();
$turn = $other;              // 让给对方
CODE],

            74 => ['Peterson 解法',
                '两个变量(想进吗 + 该谁了)配合,纯软件实现正确互斥。',
                <<<'CODE'
$interested[$me] = true; $turn = $other;
while ($interested[$other] && $turn == $other) {
    /* 等 */
}
critical();
$interested[$me] = false;
CODE],

            75 => ['TSL 指令',
                '硬件原子指令:读+写一气呵成,无空隙可插。',
                <<<'CODE'
function TSL(&$lock) {        // 硬件保证原子
    $old = $lock; $lock = 1; return $old;
}
while (TSL($lock) != 0) { }   // 拿到锁前自旋
critical();
$lock = 0;
CODE],

            76 => ['睡眠与唤醒',
                '进不去就睡(让出CPU),条件满足被唤醒,省 CPU。',
                <<<'CODE'
if ($count == 0) sleep_proc();   // 没货就睡
// 生产者放货后:
$count++;
wakeup($consumer);               // 喊醒消费者
CODE],

            77 => ['生产者-消费者问题',
                '满了生产者等、空了消费者等,用信号量协调。',
                <<<'CODE'
// 生产者
sem_acquire($empty); sem_acquire($mutex);
$buffer[] = $item;               // 放入
sem_release($mutex); sem_release($full);
CODE],

            78 => ['信号量',
                '带计数的令牌:P 减1(没了就等),V 加1(唤醒等待者)。',
                <<<'CODE'
$sem = sem_get(1234, 10);    // 10 个资源
sem_acquire($sem);           // P: 减1, 没了就睡
/* 使用资源 ... */
sem_release($sem);           // V: 加1, 唤醒排队者
CODE],

            79 => ['互斥量(Mutex)',
                '信号量的二值版:只有锁住/没锁,专做互斥。',
                <<<'CODE'
$mutex = sem_get(1, 1);      // 初值1
sem_acquire($mutex);         // lock
$shared++;
sem_release($mutex);         // unlock
CODE],

            80 => ['用户空间的互斥量',
                '无争用时纯用户态(一条原子指令),真冲突才进内核(类 futex)。',
                <<<'CODE'
// CAS: 比较并交换, 原子
if (atomic_cas($lock, 0, 1)) {
    critical();              // 没人抢, 用户态直接拿到, 飞快
} else {
    futex_wait($lock);      // 真冲突, 才求助内核睡觉
}
CODE],

            81 => ['管程',
                '把数据和操作打包,运行时自动互斥,不用手写锁。',
                <<<'CODE'
class Account {              // 类似 Java 的 synchronized
    private int $bal = 0;
    public function add(int $n) {   // 自动互斥
        $this->bal += $n;
    }
}
CODE],

            82 => ['消息传递',
                '不共享内存,靠 send/receive 交换消息,适合跨机器。',
                <<<'CODE'
$q = msg_get_queue(1234);
msg_send($q, 1, "你好");                 // 发
msg_receive($q, 1, $type, 100, $msg);   // 收
echo $msg;
CODE],

            83 => ['屏障(Barrier)',
                '所有进程到齐了才一起放行进入下一阶段。',
                <<<'CODE'
$arrived = 0; $N = 3;
function barrier() {
    global $arrived, $N;
    $arrived++;
    while ($arrived < $N) { /* 等所有人到齐 */ }
}
CODE],

            84 => ['避免锁:读-复制-更新(RCU)',
                '读不加锁直接读;写者复制一份改好再原子替换。',
                <<<'CODE'
$config = ['v' => 1];        // 读者直接读, 零等待
$new = $config;              // 写者: 复制
$new['v'] = 2;               //       修改副本
$config = $new;             //       原子替换, 读者无感
CODE],

            85 => ['调度的引入',
                '就绪一堆,CPU 一个,调度器按算法选下一个跑。',
                <<<'CODE'
$ready = ['p1', 'p2', 'p3'];
$next  = scheduler($ready);  // 按策略选一个
run($next);
CODE],

            86 => ['进程行为',
                '分 CPU 密集型(闷头算)和 I/O 密集型(老等),要区别对待。',
                <<<'CODE'
$p1 = ['type'=>'CPU密集', 'ioRatio'=>0.1];  // 一直在算
$p2 = ['type'=>'IO密集',  'ioRatio'=>0.9];  // 老在等IO
CODE],

            87 => ['何时调度',
                '退出、阻塞、时间片到、I/O 中断来时都要重新调度。',
                <<<'CODE'
on('process_exit',  'schedule');   // 进程退出
on('process_block', 'schedule');   // 进程阻塞
on('timer_tick',    'schedule');   // 时间片用完
CODE],

            88 => ['调度算法的分类',
                '非抢占(跑到自己停) vs 抢占(时间到强行切)。',
                <<<'CODE'
// 非抢占
function nonPreempt($p){ while(!$p->done) $p->run(); }
// 抢占
function preempt($p, $slice){ $p->run($slice); switchOut(); }
CODE],

            89 => ['调度算法的目标',
                '批处理求吞吐、交互求响应、实时求不超时。',
                <<<'CODE'
$throughput  = $jobsDone / $time;       // 批处理
$response    = $firstResponseTime;      // 交互
$deadlineMet = $onTime / $total;        // 实时
CODE],

            90 => ['批处理系统中的调度',
                '没人催,追求平均周转时间最短、CPU 满载。',
                <<<'CODE'
// 周转时间 = 完成时刻 - 到达时刻
$turnaround = array_sum($finishTimes) / count($finishTimes);
echo "平均周转: $turnaround\n";
CODE],

            91 => ['先来先服务(FCFS)',
                '谁先到谁先跑,不插队,但大任务堵后面。',
                <<<'CODE'
$queue = ['A', 'B', 'C'];    // 按到达顺序
foreach ($queue as $job) {
    run($job);               // 不插队
}
CODE],

            92 => ['最短作业优先(SJF)',
                '按预计耗时升序,平均等待最短。',
                <<<'CODE'
usort($jobs, fn($a, $b) => $a['len'] - $b['len']);  // 短的排前
foreach ($jobs as $j) run($j);
CODE],

            93 => ['最短剩余时间优先(SRTN)',
                'SJF 抢占版:新任务更短就立刻抢占当前。',
                <<<'CODE'
if ($new['len'] < $current['remaining']) {
    switchTo($new);          // 新任务剩得更少, 抢占
}
CODE],

            94 => ['交互式系统中的调度',
                '抢占+时间片,保证每个进程很快轮到一小段 CPU。',
                <<<'CODE'
$quantum = 20;               // 时间片 20ms
foreach ($procs as $p) {
    $p->run($quantum);       // 轮流给一小片
}
CODE],

            95 => ['轮转调度(Round Robin)',
                '发固定时间片,用完排队尾循环。',
                <<<'CODE'
$q = new SplQueue();
foreach ($procs as $p) $q->enqueue($p);
while (!$q->isEmpty()) {
    $p = $q->dequeue();
    $p->run($slice);
    if (!$p->done) $q->enqueue($p);   // 没完排队尾
}
CODE],

            96 => ['优先级调度',
                '高优先先跑;等久了升优先级,防饿死。',
                <<<'CODE'
usort($ready, fn($a, $b) => $b['prio'] - $a['prio']);  // 高优先在前
run($ready[0]);
$ready[0]['prio']--;         // 跑过降一点; 等久的会升
CODE],

            97 => ['多级队列',
                '分若干优先级队列,高队先伺候,同队内部轮转。',
                <<<'CODE'
$queues = [0 => [], 1 => [], 2 => []];  // 0 最高
foreach ($queues as $lvl => $q) {
    if ($q) { run(array_shift($queues[$lvl])); break; }
}
CODE],

            98 => ['最短进程优先',
                '用历史加权预测下次运行长度(老化公式)。',
                <<<'CODE'
// 估计值 = a*上次实际 + (1-a)*上次估计
$a = 0.5;
$est = $a * $lastRun + (1 - $a) * $lastEst;
CODE],

            99 => ['保证调度',
                '承诺每人 1/n CPU,谁拿少了优先补谁。',
                <<<'CODE'
$deserved = $totalCpu / $nUsers;        // 每人应得
$ratio = $actualUsed / $deserved;       // 实得/应得
$next = array_keys($ratio, min($ratio))[0];  // 拿最少的先跑
CODE],

            100 => ['彩票调度',
                '发彩票随机抽,票多者占比高。',
                <<<'CODE'
$tickets = ['A' => 80, 'B' => 20];
$win = mt_rand(1, 100);
$cur = 0;
foreach ($tickets as $p => $t) {
    $cur += $t;
    if ($win <= $cur) { echo "中奖: $p\n"; break; }
}
CODE],

            101 => ['公平分享调度',
                '公平单位是用户而非进程,先按用户平分。',
                <<<'CODE'
$perUser = $cpu / count($users);        // 每个用户应得
foreach ($users as $u) {
    $u->share($perUser);                // 用户内部再分给其进程
}
CODE],

            102 => ['实时系统中的调度',
                '必须在截止期前完成,常用最早截止期优先(EDF)。',
                <<<'CODE'
usort($tasks, fn($a, $b) => $a['deadline'] - $b['deadline']);
run($tasks[0]);              // 最早到期的先跑
CODE],

            103 => ['线程调度',
                '切同进程内线程便宜,切到别进程的线程贵。',
                <<<'CODE'
if ($next->proc === $current->proc) {
    cheapSwitch();           // 同进程: 不换地址空间, 便宜
} else {
    expensiveSwitch();       // 跨进程: 要换内存空间, 贵
}
CODE],

            104 => ['哲学家就餐问题',
                '破死锁:奇数号先拿左、偶数号先拿右,避免全抢同一边。',
                <<<'CODE'
if ($id % 2 == 0) { take($right); take($left); }
else              { take($left);  take($right); }
eat();
putDown($left); putDown($right);
CODE],

            105 => ['读者-写者问题',
                '多读可并发,写要独占;首个读者上写锁、末个读者解锁。',
                <<<'CODE'
sem_acquire($rmutex);
if (++$readers == 1) sem_acquire($wlock);   // 第一个读者锁住写
sem_release($rmutex);
read();
sem_acquire($rmutex);
if (--$readers == 0) sem_release($wlock);    // 最后一个读者放开写
sem_release($rmutex);
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} 条(本章 50~105)\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【%02d】%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 ProcThreadBook();
$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 等多样性计算架构

更多推荐