diff --git a/notes/计算机操作系统 - 进程管理.md b/notes/计算机操作系统 - 进程管理.md
index 55fcb267..dce7888e 100644
--- a/notes/计算机操作系统 - 进程管理.md
+++ b/notes/计算机操作系统 - 进程管理.md
@@ -5,8 +5,8 @@
* [1. 进程](#1-进程)
* [2. 线程](#2-线程)
* [3. 区别](#3-区别)
- * [4. 协程](#4-协程)
- * [5. 上下文切换](#5-上下文切换)
+ * [4. 上下文切换](#4-上下文切换)
+ * [5. 协程](#5-协程)
* [进程状态的切换](#进程状态的切换)
* [进程调度算法](#进程调度算法)
* [1. 批处理系统](#1-批处理系统)
@@ -74,7 +74,21 @@ QQ 和浏览器是两个进程,浏览器进程里面有很多线程,例如 H
- 任务之间需要隔离、一个出错不能拖垮其它任务时用多进程,例如 Chrome 把网页放在单独的渲染进程中,一个网页崩溃不影响浏览器和其它标签页;执行不受信任的代码(例如 Agent 运行模型生成的脚本)也应该放在单独的进程甚至容器中;
- 任务之间要频繁共享数据、对创建和切换的开销敏感时用多线程,例如 Web 服务器用线程池处理请求,各线程直接读写同一份本地缓存。
-### 4. 协程
+### 4. 上下文切换
+
+上下文切换指的是 CPU 从执行一个线程改为执行另一个线程时,把当前线程的寄存器、程序计数器、栈指针等保存起来,再恢复另一个线程上次保存的值。以下情况会发生上下文切换:
+
+- 时间片用完,被调度程序换下;
+- 线程主动阻塞,例如等待 I/O、等待锁、调用 sleep();
+- 更高优先级的线程进入就绪状态。
+
+需要注意的是,系统调用只是从用户态进入内核态,执行完后通常回到同一个线程,不一定发生上下文切换。
+
+同一进程中的线程切换不需要换地址空间;切换到另一个进程时还要切换页表,TLB 和 CPU 缓存中原来的地址映射和数据对新进程没有用,要重新加载,这部分间接开销通常比保存寄存器更大。x86 的 PCID 可以给 TLB 表项标上所属的地址空间,切换页表时不必清空整个 TLB,没有 PCID 时每次切换页表都会清空 TLB。
+
+例如把两个执行流固定在同一个 CPU 上,通过两根管道来回传 1 个字节,让它们交替运行,两个线程之间、两个进程之间每次切换都约 1.1 微秒,包含一次 read 或 write 系统调用(Linux 实测,见[附录 A1](#a1-上下文切换))。数据量很小时两者几乎没有差别,进程切换的额外开销主要在工作集较大时体现出来。线程过多、频繁阻塞时,CPU 会把相当一部分时间花在切换上,可以用 vmstat 的 cs 列或 pidstat -w 观察每秒的切换次数。
+
+### 5. 协程
协程(Coroutine)是在用户态实现的轻量级线程,由程序自己调度,而不是由操作系统内核调度。协程在等待 I/O 时主动让出执行权,运行时再把其它协程调度到同一个线程上执行。
@@ -97,20 +111,6 @@ Java 21 正式引入的虚拟线程([JEP 444](https://openjdk.org/jeps/444))
协程也不能无限创建:每个协程的栈和状态都要占内存,例如把最大堆设为 256 MB 时,启动 100 万个阻塞的虚拟线程会抛出 OutOfMemoryError。协程适合大量任务都在等待 I/O 的场景,例如每个请求都要调用下游接口、查询数据库;CPU 密集的任务没有等待可以让出,开再多协程也只能由少量线程轮流执行,不会更快。
-### 5. 上下文切换
-
-上下文切换指的是 CPU 从执行一个线程改为执行另一个线程时,把当前线程的寄存器、程序计数器、栈指针等保存起来,再恢复另一个线程上次保存的值。以下情况会发生上下文切换:
-
-- 时间片用完,被调度程序换下;
-- 线程主动阻塞,例如等待 I/O、等待锁、调用 sleep();
-- 更高优先级的线程进入就绪状态。
-
-需要注意的是,系统调用只是从用户态进入内核态,执行完后通常回到同一个线程,不一定发生上下文切换。
-
-同一进程中的线程切换不需要换地址空间;切换到另一个进程时还要切换页表,TLB 和 CPU 缓存中原来的地址映射和数据对新进程没有用,要重新加载,这部分间接开销通常比保存寄存器更大。x86 的 PCID 可以给 TLB 表项标上所属的地址空间,切换页表时不必清空整个 TLB,没有 PCID 时每次切换页表都会清空 TLB。
-
-例如把两个执行流固定在同一个 CPU 上,通过两根管道来回传 1 个字节,让它们交替运行,两个线程之间、两个进程之间每次切换都约 1.1 微秒,包含一次 read 或 write 系统调用(Linux 实测,见[附录 A1](#a1-上下文切换))。数据量很小时两者几乎没有差别,进程切换的额外开销主要在工作集较大时体现出来。线程过多、频繁阻塞时,CPU 会把相当一部分时间花在切换上,可以用 vmstat 的 cs 列或 pidstat -w 观察每秒的切换次数。
-
## 进程状态的切换
@@ -468,114 +468,7 @@ void writer() {
}
```
-以下内容由 [@Bandi Yugandhar](https://github.com/yugandharbandi) 提供。
-
-上面的实现可能导致写者饥饿。下面的实现是写者优先的:写者进入队列之后,只需要等待在它之前进入的读者,不会被之后到达的读者一直阻塞。
-
-```c
-int readcount, writecount; //(initial value = 0)
-semaphore rmutex, wmutex, readLock, resource; //(initial value = 1)
-
-//READER
-void reader() {
-
- down(&readLock); // reader is trying to enter
- down(&rmutex); // lock to increase readcount
- readcount++;
- if (readcount == 1)
- down(&resource); //if you are the first reader then lock the resource
- up(&rmutex); //release for other readers
- up(&readLock); //Done with trying to access the resource
-
-
-//reading is performed
-
-
- down(&rmutex); //reserve exit section - avoids race condition with readers
- readcount--; //indicate you're leaving
- if (readcount == 0) //checks if you are last reader leaving
- up(&resource); //if last, you must release the locked resource
- up(&rmutex); //release exit section for other readers
-}
-
-//WRITER
-void writer() {
-
- down(&wmutex); //reserve entry section for writers - avoids race conditions
- writecount++; //report yourself as a writer entering
- if (writecount == 1) //checks if you're first writer
- down(&readLock); //if you're first, then you must lock the readers out. Prevent them from trying to enter CS
- up(&wmutex); //release entry section
-
-
- down(&resource); //reserve the resource for yourself - prevents other writers from simultaneously editing the shared resource
- //writing is performed
- up(&resource); //release file
-
-
- down(&wmutex); //reserve exit section
- writecount--; //indicate you're leaving
- if (writecount == 0) //checks if you're the last writer
- up(&readLock); //if you're last writer, you must unlock the readers. Allows them to try enter CS for reading
- up(&wmutex); //release exit section
-}
-```
-
-每个读者都必须先获取 readLock,而只有第一个写者需要获取 readLock,并且直到队列中没有写者时才会释放,因此这种实现可能导致读者饥饿。
-
-以上两种实现总有一方可能饥饿。下面的实现使用一个先进先出的 serviceQueue 让读者和写者按到达顺序排队,获取共享数据的锁总能在有限时间内完成,因此不会出现饥饿。
-
-```c
-int readCount; // init to 0; number of readers currently accessing resource
-
-// all semaphores initialised to 1
-Semaphore resourceAccess; // controls access (read/write) to the resource
-Semaphore readCountAccess; // for syncing changes to shared variable readCount
-Semaphore serviceQueue; // FAIRNESS: preserves ordering of requests (signaling must be FIFO)
-
-void writer()
-{
- down(&serviceQueue); // wait in line to be serviced
- //
- down(&resourceAccess); // request exclusive access to resource
- //
- up(&serviceQueue); // let next in line be serviced
-
- //
- writeResource(); // writing is performed
- //
-
- //
- up(&resourceAccess); // release resource access for next reader/writer
- //
-}
-
-void reader()
-{
- down(&serviceQueue); // wait in line to be serviced
- down(&readCountAccess); // request exclusive access to readCount
- //
- if (readCount == 0) // if there are no readers already reading:
- down(&resourceAccess); // request resource access for readers (writers blocked)
- readCount++; // update count of active readers
- //
- up(&serviceQueue); // let next in line be serviced
- up(&readCountAccess); // release access to readCount
-
- //
- readResource(); // reading is performed
- //
-
- down(&readCountAccess); // request exclusive access to readCount
- //
- readCount--; // update count of active readers
- if (readCount == 0) // if there are no readers left:
- up(&resourceAccess); // release resource access for all
- //
- up(&readCountAccess); // release access to readCount
-}
-
-```
+这种实现在读者源源不断时可能导致写者饥饿,写者优先和按到达顺序排队的公平实现见[进阶:读者-写者问题](计算机操作系统%20-%20进程管理%20-%20进阶.md#读者-写者问题)。
## 进程通信