现代操作系统=进程与线程
·
<?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);
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)