进程管理API
操作系统通过fork()
和execve()
创建进程。通过mmap()
修改地址空间
操作系统通过一系列系统调用(System Calls)来提供对进程的管理,这些系统调用组成了进程管理API。以下是几个最重要的API:
1. 创建进程:fork()
fork()
是 Unix/Linux 系统中最基本的进程创建方式。当一个进程调用fork()
时,操作系统会创建一个几乎完全相同的子进程。- 返回值:
fork()
是一个神奇的调用,它会返回两次。- 在父进程中,
fork()
返回子进程的PID(进程ID),一个大于0的整数。 - 在子进程中,
fork()
返回0。 - 如果创建失败,
fork()
返回-1。
- 在父进程中,
- 地址空间:
fork()
调用后,父子进程拥有各自独立的地址空间。父进程对变量的修改不会影响子进程,反之亦然。但在调用fork()
之前,父进程打开的文件描述符会被子进程继承。
2. 运行新程序:exec()
exec()
系统调用族(如execvp()
)的作用是加载并运行一个全新的程序。exec()
不会创建新的进程。它会用一个新的程序映像(代码、数据等)替换当前进程的映像。- 通常,
exec()
配合fork()
一起使用。先用fork()
创建一个子进程,然后在子进程中调用exec()
来运行另一个程序,这样既能创建新进程,又能加载新程序。
3. 等待进程完成:wait()
wait()
系统调用用于让父进程等待子进程结束。- 当父进程调用
wait()
时,它会暂停执行,直到它的某个子进程终止。 wait()
的作用是防止“僵尸进程(zombie process)”的出现。当子进程终止后,它会进入僵尸状态,但仍然保留一些信息(如退出状态码)。父进程通过调用wait()
可以获取这些信息,并让操作系统回收子进程的资源。
4. 结束进程:exit()
exit()
系统调用用于终止一个进程的执行。exit()
的参数是一个整数,称为退出状态码。通常,0 表示成功,非0表示失败。- 当一个进程调用
exit()
后,操作系统会回收它的大部分资源,但进程ID和退出状态码会保留下来,直到父进程通过wait()
来处理。
虚拟化内存
虚拟内存是一种内存管理技术,它为每个进程提供了一个独立的、连续的、巨大的逻辑地址空间,而这个空间实际上是映射到物理内存(以及部分硬盘空间)上的。
简单来说,它让程序“感觉”自己拥有了一整块非常大的、独占的内存,但实际上物理内存可能是分散的、有限的,甚至部分数据还存放在硬盘上。
对象
文件描述符从最小未分配的描述符开始分配
同步和互斥的区别
1. 核心目标不同
- 互斥 (Mutual Exclusion): 强调的是 “排他性” 或 “隔离”。
- 目标: 确保共享资源在任何时刻 只被一个 执行流(线程/进程)访问。
- 解决的问题: “我们不要同时访问这个东西,免得搞砸了。” 它防止的是“冲突”。
- 关键词: 独占、唯一、锁。
- 同步 (Synchronization): 强调的是 “协作” 或 “时序”。
- 目标: 控制多个执行流之间执行的 相对顺序,让它们能够有效、正确地协同工作来完成一个任务。
- 解决的问题: “你必须等我做完这件事,你才能开始做你的事。” 它保证的是“秩序”。
- 关键词: 协作、顺序、等待、通知。
2. 生活中的比喻:两位厨师做一道菜
想象有两位厨师(线程A和线程B)合作做一道菜:“番茄炒蛋”。
这道菜有两个主要步骤:
- 厨师A负责切番茄。
- 厨师B负责炒鸡蛋,并在鸡蛋炒好后,加入切好的番茄一起翻炒。
在这个场景中:
- 互斥是什么?
- 厨房里只有 一把菜刀(共享资源)。
- 当厨师A在用菜刀切番茄时,厨师B就不能用这把菜刀。反之亦然。
- 这把菜刀的使用规则就是 “互斥”。我们用一个“正在使用”的牌子(互斥锁)来保证这一点。谁拿到牌子谁用刀。
- 同步是什么?
- 整个做菜流程有一个明确的 顺序:厨师B 必须 等到厨师A把番茄切好后,才能把番茄倒入锅中。
- 如果厨师A还没切番茄,厨师B就把鸡蛋炒好了,此时他不能继续,必须停下来等待。
- 这种为了保证任务正确完成而建立的 执行时序上的依赖关系,就是 “同步”。我们可以用一个“番茄切好了”的信号(条件变量)来协调他们。厨师A切好后发出信号,厨师B接收到信号才继续工作。
3. 关系:从属关系
同步是一个宏观的目标,而互斥是实现这个目标的一种手段。
- 你想让两位厨师 同步 协作,完美地做出番茄炒蛋。
- 为了达成这个目标,你需要解决两个问题:
- 顺序问题:先切番茄,再放番茄。(这是纯粹的同步问题)
- 资源冲突问题:菜刀不能同时用。(这是互斥问题)
所以你看,为了完成整个“同步”任务,你需要用到“互斥”这个工具来管理资源。因此,我们可以说互斥是同步这个大范畴内的一个子问题。
4. 总结表格
特性 | 互斥 (Mutual Exclusion) | 同步 (Synchronization) |
---|---|---|
关系 | 是同步的一种特殊情况 | 更广泛的概念,包含互斥 |
核心目的 | 访问控制:保证资源在同一时间点不被多个线程访问 | 时序协调:保证多个线程按照预定的顺序执行 |
关注点 | 空间上隔离,防止冲突 | 时间上有序,协同工作 |
好比 | “不要同时进这个门” | “请按顺序排队办事” |
主要工具 | 互斥锁 (Mutex) | 条件变量 (Condition Variable)、信号量 (Semaphore) |
导出到 Google 表格
所以,下次当你考虑并发问题时,可以这样思考:
- 这些线程之间需要 协作 吗?它们执行的顺序有要求吗? --> 如果是,你需要 同步。
- 在协作过程中,它们会访问 共享 的东西吗?需要防止它们同时访问吗? --> 如果是,你需要 互斥 来保护那个共享的东西。
PV操作
PV操作的顺序
V 操作:只释放,不阻塞,顺序无所谓。
P 操作:会申请,可能阻塞。当多个 P 操作在一起时,错误的顺序可能导致循环等待,从而引发死锁。
有临界资源的时候要使用一个互斥P来保证只有一个线程使用
同步P vs 互斥P 法则:
- 先执行同步P (
P(full)
) 是为了检查前提条件。如果条件不满足,就不去持有任何锁,直接等待即可,不影响别人。 - 后执行互斥P (
P(mutex)
) 是为了保护临界操作。只有在确认可以进行操作后,才去申请锁,并且操作完立刻释放。
辨别互斥P和同步P
特征 | 互斥 P (Mutex P) | 同步 P (Sync P) |
---|---|---|
目的 | 锁门 (Locking),防止冲突 | 等消息 (Waiting),保证顺序 |
信号量初值 | 通常是 1 | 通常是 0 或 N (N>1) |
P/V 配对位置 | P 和 V 在同一个进程内 | P 和 V 在不同进程之间 |