1. 进程(Process)
1.1 定义
进程是资源分配的基本单位,拥有独立的地址空间、文件描述符、代码段、数据段和堆栈。
进程地址空间布局:
高地址 → ┌─────────────┐
│ 栈(Stack) │ ← 局部变量、函数调用,向下增长
│ ↓ │
│ 堆(Heap) │ ← 动态分配,向上增长
│ ↑ │
│ BSS(未初始化数据)│
│ 数据段(全局/静态)│
│ 代码段(Text) │
低地址 → └─────────────┘
1.2 进程控制块(PCB)
PCB 是操作系统管理进程的核心数据结构:
| 字段 | 说明 |
|---|---|
| 进程 ID(PID) | 唯一标识 |
| 状态 | 就绪、运行、阻塞、创建、终止 |
| 程序计数器(PC) | 下一条执行指令地址 |
| 寄存器 | CPU 寄存器状态快照 |
| 内存信息 | 页表/段表基址、代码/数据/堆栈地址 |
| 调度信息 | 优先级、时间片、等待时间 |
| 文件描述符表 | 打开的文件 |
| 信号处理 | 待处理的信号与处理函数 |
1.3 进程状态转换
创建
↓
[就绪] ←────── 时间片到
↓ ↑
被调度选择 [运行]
↓ ↓
[运行] ───→ [阻塞](等待 I/O、信号量)
↓
I/O 完成
↓
[就绪]
1.4 进程创建(Linux)
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
return 1;
} else if (pid == 0) {
// 子进程
printf("Child PID: %d, Parent PID: %d\n", getpid(), getppid());
execlp("ls", "ls", "-l", NULL); // 替换为 ls -l
} else {
// 父进程
int status;
waitpid(pid, &status, 0); // 等待子进程结束
printf("Child exited with status: %d\n", WEXITSTATUS(status));
}
return 0;
}
fork()使用写时复制(COW):子进程复制父进程页表,仅当写入时才复制物理页。
2. 线程(Thread)
2.1 定义
线程是 CPU 调度的基本单位,同一进程的线程共享代码段、数据段、堆和文件描述符表,仅独享线程栈、程序计数器和寄存器。
进程 vs 线程资源对比:
进程 同一进程内的线程
地址空间 独立 共享
堆 独立 共享
全局变量 独立 共享
栈 独立 独立(线程栈)
文件描述符 独立 共享
信号处理 独立 共享
2.2 Linux 线程实现
Linux 中线程通过 clone() 实现,是轻量级进程(LWP):
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
int thread_func(void *arg) {
printf("Thread running, arg: %s\n", (char*)arg);
return 0;
}
int main() {
char stack[8192];
// CLONE_VM: 共享地址空间
// CLONE_FS: 共享文件系统信息
// CLONE_FILES: 共享文件描述符表
clone(thread_func, stack + 8192,
CLONE_VM | CLONE_FS | CLONE_FILES,
"hello");
return 0;
}
2.3 POSIX 线程(pthread)
#include <pthread.h>
#include <stdio.h>
void *worker(void *arg) {
int num = *(int*)arg;
printf("Thread %d working\n", num);
pthread_exit(NULL);
}
int main() {
pthread_t tid;
int arg = 42;
pthread_create(&tid, NULL, worker, &arg);
pthread_join(tid, NULL); // 等待线程结束
printf("Main thread done\n");
return 0;
}
3. 线程模型
| 模型 | 特点 | 代表 |
|---|---|---|
| 一对一(1:1) | 每个用户线程对应一个内核线程 | Linux pthread、Windows |
| 多对一(M:1) | 多个用户线程映射到一个内核线程 | 早期 Green Thread |
| 多对多(M:N) | 多用户线程映射到多内核线程 | Go goroutine、Erlang |
1:1 模型:
用户线程 T1 ──→ 内核线程 K1
用户线程 T2 ──→ 内核线程 K2
(线程切换由内核调度,线程阻塞不影响其他线程)
M:1 模型:
用户线程 T1 ──┐
用户线程 T2 ──┼──→ 内核线程 K1
用户线程 T3 ──┘
(线程切换在用户态,但一个线程阻塞全阻塞)
M:N 模型:
用户线程 T1 ──┐ ┌──→ 内核线程 K1
用户线程 T2 ──┼─────┼──→ 内核线程 K2
用户线程 T3 ──┘ └──→ 内核线程 K3
(灵活调度,结合了两种模型的优点)
4. 进程间通信(IPC)
4.1 管道(Pipe)
#include <unistd.h>
#include <stdio.h>
int main() {
int pipefd[2];
pipe(pipefd);
pid_t pid = fork();
if (pid == 0) {
close(pipefd[1]); // 子进程关闭写端
char buf[64];
read(pipefd[0], buf, sizeof(buf));
printf("Child received: %s\n", buf);
close(pipefd[0]);
} else {
close(pipefd[0]); // 父进程关闭读端
write(pipefd[1], "Hello from parent", 17);
close(pipefd[1]);
wait(NULL);
}
return 0;
}
4.2 共享内存 + 信号量
#include <sys/shm.h>
#include <sys/sem.h>
// 共享内存:最快的 IPC(无需内核拷贝)
int shmid = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0666);
void *addr = shmat(shmid, NULL, 0);
// 信号量同步
int semid = semget(IPC_PRIVATE, 1, IPC_CREAT | 0666);
// P 操作(等待)、V 操作(信号)
struct sembuf p = {0, -1, SEM_UNDO};
struct sembuf v = {0, 1, SEM_UNDO};
semop(semid, &p, 1); // P
// 访问共享内存...
semop(semid, &v, 1); // V
4.3 消息队列
#include <sys/msg.h>
struct msg_buffer {
long mtype;
char mtext[256];
};
int msgid = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
struct msg_buffer msg = {1, "Hello"};
msgsnd(msgid, &msg, sizeof(msg.mtext), 0);
msgrcv(msgid, &msg, sizeof(msg.mtext), 1, 0);
4.4 IPC 方式对比
| 方式 | 速度 | 数据格式 | 同步方式 | 适用场景 |
|---|---|---|---|---|
| 管道 | 中 | 字节流 | 读写阻塞 | 父子进程通信 |
| 命名管道(FIFO) | 中 | 字节流 | 读写阻塞 | 无亲缘关系进程 |
| 共享内存 | 最快(无拷贝) | 原始内存 | 需额外同步 | 大量数据、高性能 |
| 消息队列 | 中 | 结构化消息 | 自带同步 | 异步消息、解耦 |
| 信号量 | — | — | 同步原语 | 互斥/同步 |
| Socket | 较慢 | 字节流/报文 | 自带 | 跨网络、跨机器 |
| 信号 | 快 | 整数 | 异步中断 | 事件通知 |
5. 并发 vs 并行
单核 CPU:
┌───┬───┬───┬───┬───┬───┐
│ P1│ P2│ P1│ P3│ P2│ P1│ → 并发(Concurrency)
└───┴───┴───┴───┴───┴───┘ 任务交替执行,逻辑上同时
多核 CPU:
Core 1: ┌───┬───┬───┐
│ P1│ P1│ P1│ → 并行(Parallelism)
Core 2: ├───┼───┼───┤ 任务真正同时执行
│ P2│ P2│ P2│
└───┴───┴───┘
参考文章
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。