Skip to content
约 0 字 · 预计阅读 0 分钟

进程与线程

什么是进程?

进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位。每个进程都有独立的内存空间、文件描述符、环境变量等资源。

进程的组成

┌─────────────────────────────────────────────────────────────┐
│                        进程控制块 (PCB)                      │
│  ┌─────────────────────────────────────────────────────────┐│
│  │ 进程标识符 (PID)                                         ││
│  │ 进程状态(就绪/运行/阻塞)                                ││
│  │ 程序计数器 (PC)                                          ││
│  │ CPU 寄存器                                               ││
│  │ CPU 调度信息(优先级、调度队列指针)                      ││
│  │ 内存管理信息(页表、基地址)                              ││
│  │ I/O 状态信息                                             ││
│  └─────────────────────────────────────────────────────────┘│
├─────────────────────────────────────────────────────────────┤
│                        进程地址空间                          │
│  ┌─────────────────────────────────────────────────────────┐│
│  │ 代码段 (.text)                                          ││
│  ├─────────────────────────────────────────────────────────┤│
│  │ 数据段 (.data/.bss)                                     ││
│  ├─────────────────────────────────────────────────────────┤│
│  │ 堆区 (Heap)                                             ││
│  ├─────────────────────────────────────────────────────────┤│
│  │ 栈区 (Stack)                                            ││
│  └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘

上述图示展示了进程的组成结构。

PCB 关键信息:

字段说明
PID进程唯一标识符
状态就绪、运行、阻塞
PC下一条指令地址
优先级调度优先级
内存指针页表、代码/数据指针
I/O 状态打开的文件、I/O 设备

进程状态转换

上述状态图展示了进程的生命周期。

进程状态转换可视化

状态说明:

状态说明
创建进程正在创建,分配 PCB
就绪进程已准备好,等待 CPU
运行进程正在执行
阻塞进程等待 I/O 或事件
终止进程执行完毕

什么是线程?

线程是CPU 调度的基本单位,是进程中的一个执行流。同一进程的多个线程共享进程的资源。

线程与进程的区别

进程 A                              进程 B
┌─────────────────────┐            ┌─────────────────────┐
│ 代码段              │            │ 代码段              │
│ 数据段              │            │ 数据段              │
│ 打开的文件          │            │ 打开的文件          │
│ ┌─────┬─────┬─────┐│            │ ┌─────┐            │
│ │线程1│线程2│线程3││            │ │线程1│            │
│ │栈   │栈   │栈   ││            │ │栈   │            │
│ │寄存器│寄存器│寄存器││            │ │寄存器│            │
│ └─────┴─────┴─────┘│            │ └─────┘            │
└─────────────────────┘            └─────────────────────┘
    共享进程资源                        独立进程资源

上述图示展示了多线程进程的结构。

详细对比:

特性进程线程
资源独立地址空间共享进程资源
通信需要 IPC直接读写共享变量
开销创建/切换开销大创建/切换开销小
安全性进程隔离,更安全一个线程崩溃可能影响整个进程
适用场景需要隔离的任务需要频繁通信的任务

进程通信(IPC)

管道

c
#include <unistd.h>

int pipe(int pipefd[2]);
// pipefd[0]: 读端
// pipefd[1]: 写端

int main(void) {
    int pipefd[2];
    pid_t pid;
    char buf[100];
    
    pipe(pipefd);
    pid = fork();
    
    if (pid == 0) {
        // 子进程:写入数据
        close(pipefd[0]);  // 关闭读端
        write(pipefd[1], "Hello from child", 17);
        close(pipefd[1]);
    } else {
        // 父进程:读取数据
        close(pipefd[1]);  // 关闭写端
        read(pipefd[0], buf, sizeof(buf));
        printf("Parent received: %s\n", buf);
        close(pipefd[0]);
    }
    return 0;
}

上述代码展示了管道通信的基本用法。

管道特点:

特点说明
单向数据只能单向流动
亲缘关系只能用于父子进程或兄弟进程
缓冲区内核缓冲区,大小有限
阻塞读空管道或写满管道会阻塞

共享内存

c
#include <sys/shm.h>

int main(void) {
    key_t key = ftok("/tmp", 'A');
    int shmid = shmget(key, 1024, IPC_CREAT | 0666);
    
    // 附加共享内存
    char *shm = shmat(shmid, NULL, 0);
    
    // 写入数据
    strcpy(shm, "Hello shared memory");
    
    // 分离共享内存
    shmdt(shm);
    
    // 删除共享内存
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}

上述代码展示了共享内存的使用方式。

IPC 方式对比:

方式速度数据量复杂度适用场景
管道简单数据流
FIFO无亲缘关系进程
共享内存大量数据交换
消息队列异步通信
信号量同步控制
Socket网络通信

线程同步

互斥锁

c
#include <pthread.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int counter = 0;

void* thread_func(void *arg) {
    for (int i = 0; i < 10000; i++) {
        pthread_mutex_lock(&mutex);
        counter++;
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

int main(void) {
    pthread_t t1, t2;
    
    pthread_create(&t1, NULL, thread_func, NULL);
    pthread_create(&t2, NULL, thread_func, NULL);
    
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    
    printf("counter = %d\n", counter);  // 20000
    return 0;
}

上述代码展示了互斥锁保护共享变量的用法。

条件变量

c
#include <pthread.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int ready = 0;

void* producer(void *arg) {
    pthread_mutex_lock(&mutex);
    ready = 1;
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);
    return NULL;
}

void* consumer(void *arg) {
    pthread_mutex_lock(&mutex);
    while (!ready) {
        pthread_cond_wait(&cond, &mutex);
    }
    printf("Consumer: ready = %d\n", ready);
    pthread_mutex_unlock(&mutex);
    return NULL;
}

上述代码展示了条件变量的使用方式。

同步机制对比:

机制用途特点
互斥锁保护临界区只有一个线程可以持有
读写锁读多写少读读并行,写独占
条件变量等待条件成立需要配合互斥锁使用
信号量资源计数可以允许多个线程访问
自旋锁短时间等待忙等待,不释放 CPU

进程调度算法

常见调度算法

算法说明优点缺点
FCFS先来先服务简单公平护航效应
SJF短作业优先平均等待时间最短需要预知执行时间
SRTF最短剩余时间优先SJF 的抢占版本开销大
优先级按优先级调度灵活可能饥饿
RR时间片轮转公平响应快时间片选择困难
多级反馈队列多队列 + 动态优先级综合各种优点实现复杂

Linux 进程调度

Linux 使用 CFS(Completely Fair Scheduler) 调度器:

c
// Linux 进程调度策略
#define SCHED_NORMAL    0   // 普通进程,CFS 调度
#define SCHED_FIFO      1   // 实时进程,先进先出
#define SCHED_RR        2   // 实时进程,时间片轮转
#define SCHED_BATCH     3   // 批处理进程
#define SCHED_IDLE      5   // 空闲进程
#define SCHED_DEADLINE  6   // 截止时间调度

上述代码展示了 Linux 的调度策略。

CFS 核心思想:

  • 使用红黑树维护可运行进程
  • 按虚拟运行时间(vruntime)排序
  • 总是选择 vruntime 最小的进程运行
  • 保证每个进程获得公平的 CPU 时间

进程创建与终止

fork 系统调用

c
#include <unistd.h>
#include <sys/wait.h>

int main(void) {
    pid_t pid = fork();
    
    if (pid < 0) {
        perror("fork failed");
        return 1;
    } else if (pid == 0) {
        // 子进程
        printf("Child process: PID = %d\n", getpid());
        execlp("/bin/ls", "ls", "-l", NULL);
        exit(0);
    } else {
        // 父进程
        printf("Parent process: PID = %d, child PID = %d\n", 
               getpid(), pid);
        wait(NULL);  // 等待子进程结束
        printf("Child process terminated\n");
    }
    return 0;
}

上述代码展示了 fork 和 exec 的使用方式。

fork 特点:

特点说明
返回值父进程返回子进程 PID,子进程返回 0
复制复制父进程的地址空间(写时复制)
资源子进程继承父进程的资源
执行顺序父子进程执行顺序不确定

写时复制(Copy-on-Write)

fork() 之前:
┌─────────────────────────────────────────────────────────────┐
│ 父进程                                                      │
│ 代码段 │ 数据段 │ 堆 │ 栈                                   │
└─────────────────────────────────────────────────────────────┘

fork() 之后(写时复制):
┌─────────────────────────────────────────────────────────────┐
│ 共享页面(只读)                                             │
│ 代码段 │ 数据段 │ 堆 │ 栈                                   │
└─────────────────────────────────────────────────────────────┘
     ↑                    ↑
     │                    │
┌────┴────┐          ┌────┴────┐
│父进程   │          │子进程   │
│页表     │          │页表     │
└─────────┘          └─────────┘

写入时:
┌─────────────────────────────────────────────────────────────┐
│ 父进程页面                    子进程页面(复制)              │
│ 数据段 │ 堆 │ 栈              数据段 │ 堆 │ 栈               │
└─────────────────────────────────────────────────────────────┘

上述图示展示了写时复制的工作原理。

总结

概念说明
进程资源分配的基本单位,有独立地址空间
线程CPU 调度的基本单位,共享进程资源
IPC进程间通信,包括管道、共享内存、消息队列等
同步线程同步机制,包括互斥锁、条件变量、信号量等
调度进程调度算法,Linux 使用 CFS

参考资料

[1] Operating System Concepts. Abraham Silberschatz

[2] Linux Kernel Development. Robert Love

[3] 深入理解 Linux 内核. Daniel P. Bovet

相关主题

基于 VitePress 构建