Skip to content

进程与线程

一、 进程 (Process):资源分配的最小单位

进程是操作系统对一个正在运行的程序的抽象。当你双击一个 .exe 文件时,操作系统就创建了一个进程。

1. 进程包含了什么?(工厂的基建设施)

每个进程都有自己独立的虚拟内存地址空间。在汇编层面,一个进程通常包含: * 代码段(Code Segment):存放编译后的二进制指令。 * 数据段(Data Segment):存放全局变量和静态变量。 * 堆(Heap):动态分配的内存。 * 栈(Stack):存放局部变量和函数调用信息。 * 资源句柄:如打开的文件、网络套接字、信号量等。

2. 核心特点:隔离性

  • 各过各的:进程 A 不能直接访问进程 B 的内存。如果你想在汇编里直接修改另一个进程的变量,CPU 会触发“保护异常”。
  • 崩溃不波离:一个进程崩溃了,通常不会影响到其他进程,因为它们的资源是物理隔离的。

二、 线程 (Thread):CPU 调度的最小单位

线程是进程内部的一个执行流。一个进程可以包含多个线程,它们共享进程的资源,但各自独立执行指令。

1. 线程共享什么?

同一个进程内的所有线程共享: * 代码段(大家执行同一份指令)。 * 数据段和堆(大家可以读写相同的全局变量)。 * 打开的文件。

2. 线程私有什么?

为了能独立跑起来,每个线程必须有自己的: * 寄存器集合(Register Set):包括 RIP (下一条指令地址)、RAX 等通用寄存器。 * 栈(Stack):每个线程有独立的局部变量空间,防止函数调用乱套。 * 状态标志位(RFLAGS)。


三、 进程 vs 线程:深度对比

维度 进程 线程
定义 资源分配的基本单位 CPU 调度的基本单位
内存 独立地址空间,互不干扰 共享所属进程的地址空间
开销 创建、销毁、切换开销大(要切页表) 创建、销毁、切换开销小
通信 复杂(管道、信号、共享内存) 简单(直接读写全局变量)
可靠性 高(一个死,其他活) 低(一个死,全家桶死)

四、 从汇编和 CPU 的视角看

写汇编代码时,实际上是在操控线程的行为。

1. 上下文切换 (Context Switch)

CPU 的核心数量是有限的(比如 8 核),但电脑里可能有几百个线程在跑。CPU 快速在多个线程间轮换,这个过程叫切换。 * 步骤: 1. 保存当前线程的寄存器(RAX, RBX, RIP, RSP...)到内存。 2. 加载下一个线程之前保存的寄存器值。 3. 跳转到新的 RIP 指向的位置。 * 汇编意义:这意味着你不能假设寄存器的值在任何时候都是永久不变的,除非你在自己的代码块内连续运行。

2. 栈的独立性

这是汇编学习者的重点。 * 操作系统在创建线程时,会为每个线程分配一块独立的内存作为栈。 * RSP 寄存器会指向当前线程的栈顶。 * 如果你在多线程环境下写汇编,改变了 RSP 但没改回来,或者跳到了别人的栈空间,就会引发严重的错误。

3. 线程安全 (Thread Safety)

既然线程共享堆内存和全局变量,如果两个工人同时搬一个零件(两个线程同时修改同一个内存地址):

; 线程 1 执行:        ; 线程 2 执行:
mov rax, [count]     mov rax, [count]
add rax, 1           add rax, 1
mov [count], rax     mov [count], rax
如果切换发生在中途,最终 count 可能只加了 1 而不是 2。这叫竞态条件(Race Condition)。在汇编中,会使用 LOCK 前缀指令来解决:
lock inc qword [count]  ; 这是一个原子操作,保证线程安全


五、 操作系统是如何创建它们的?

在 Linux 环境下,通过系统调用(Syscall)创建: * fork:创建一个全新的进程。它会复制父进程的所有内存,但两者的内存地址之后不再相关。 * clone(pthread_create 的底层):创建一个线程。它指向父进程的内存地址。

总结

  • 进程是“容器”,负责找系统要钱、要地皮(内存、文件)。
  • 线程是“执行者”,负责拿着 CPU 给的指令在进程的地皮上干活。
  • 汇编程序员直接面对的是线程的寄存器和栈。