1. 需要了解的概念
1.1 关于进程
进程是在计算机上运行的一个程序实例,它包括程序的代码、数据集以及操作系统为该程序分配的资源(如内存、文件句柄等)。
每个进程都有一个唯一的标识符,即进程ID(PID),并且有自己的地址空间、状态信息和其他属性。
进程之间的资源是隔离的,这意味着一个进程中的错误不会影响到其他进程。
进程间通信的方法包括信号、管道、消息队列、共享内存等。这些方法允许不同进程之间交换数据和同步操作。
进程是操作系统调度和资源分配的基本单位。
进程的核心特点:
- - 独立性:每个进程都有自己独立的内存空间,这使得进程之间的通信需要通过特定的机制(如管道、套接字等)进行。
- - 开销:创建一个新进程涉及分配新的地址空间和其他系统资源,因此创建进程的开销相对较大。
- - 安全性:由于进程之间的隔离,一个进程崩溃不会导致其他进程受到影响,提高了系统的稳定性。
- - 调度:操作系统负责调度进程的执行,确保每个进程都能得到一定的CPU时间。
-
1.2 线程
线程是进程内的一个执行单元,它是比进程更小的执行单位。一个进程可以有多个线程,所有这些线程共享相同的地址空间和其他资源(如打开的文件)。线程通常称为轻量级进程(Lightweight Process, LWP),因为它们之间的切换比进程切换所需的开销要小得多。
线程的核心特点:
- 共享资源:线程共享进程的所有资源,包括代码段、数据段和堆栈等,这使得线程之间的通信非常高效。
- 上下文切换:由于线程共享相同的地址空间,它们之间的上下文切换比进程之间的切换要快。
- 开销:创建线程的开销小于创建进程的开销。
- 依赖性:如果一个线程发生错误,整个进程可能都会受到影响,从而降低了系统的稳定性。
小结:进程提供了一种安全且隔离的执行环境,适合于需要高稳定性和资源隔离的应用场景;而线程则提供了更高效的内部通信和执行方式,适用于需要快速响应和高并发的应用场景。
1.3 进程如何被创建
进程的创建通常通过一个现有的进程(父进程)调用 fork() 系统调用来完成。fork() 创建一个与父进程几乎完全相同的副本(子进程),这个副本继承了父进程的数据段、堆栈、打开的文件描述符等资源,但每个进程都有自己的独立地址空间。之后,子进程可以调用 exec() 系列函数来替换当前进程映像,执行新的程序。
创建进程的过程:
调用 fork():父进程调用 fork() 系统调用,操作系统为新进程分配必要的资源,并复制父进程的上下文。
创建子进程:fork() 返回后,在父进程中返回的是子进程的 PID,在子进程中返回的是 0。
执行新程序:子进程通常会调用 exec() 函数族(如 execl, execle, execv, execve 等)来执行新的程序,替换当前进程的内容。
1.4 进程在Linux中可能的状态
进程在其生命周期中可能会处于多种状态,以下是 Linux 中常见的几种进程状态:
运行状态 (Running):进程正在 CPU 上执行。
就绪状态 (Ready):进程已经准备好运行,但在等待 CPU 时间片。
阻塞状态 (Blocked):进程正在等待某种条件的发生,例如等待 I/O 操作完成或其他进程的信号。
睡眠状态 (Sleeping):进程在等待某个事件发生前不会运行。
僵尸状态 (Zombie):子进程已经结束,但父进程还没有调用 wait() 或 waitpid() 来获取子进程的退出状态。
停止状态 (Stopped):进程已经被暂停,通常是因为接收到 SIGSTOP 信号。
追踪状态 (Tracing):进程正在被调试器追踪。