一、 进程的基本概念
在 Linux 系统中,进程是程序的一次执行过程。它是操作系统进行资源分配和调度的基本单位。当一个程序被加载到内存中并开始运行时,它就变成了一个进程。
- 程序 :指的是存储在磁盘上的可执行文件(例如
/bin/ls),它是静态的指令集合。 - 进程:指的是程序正在运行的一个实例,它拥有独立的内存空间、寄存器状态、程序计数器以及堆栈等。它是动态的执行实体。
例如,你可以多次运行同一个程序(如 ls),每次运行都会创建一个新的、独立的进程。
二、 进程的结构
一个 Linux 进程通常包含以下几个核心部分:
- 代码段:存放程序的可执行机器指令,存放CPU执行的机器指令,可共享、只读,常量数据在编译时在代码段中分配空间,局部数据将在运行时在栈区分配空间,编译时不分配空间。
- 数据段:存放已初始化的全局变量和静态变量。
- 堆 :存放动态分配的内存(如
malloc分配的空间),由程序员管理其生命周期。 - 栈:存放函数的局部变量、函数参数、返回地址等信息,随着函数的调用和返回动态变化。
- 进程控制块 :操作系统内核中维护的一个数据结构(通常称为
task_struct),用于记录进程的状态信息(如进程ID、父进程ID、优先级、状态、打开的文件描述符、内存映射等)。

举例:一个简单的 C 程序结构
c
#include <stdio.h>
#include <stdlib.h>
int global_var = 10; // 存储在数据段
int main() {
int stack_var; // 存储在栈
int *heap_var = (int *)malloc(sizeof(int)); // 指向堆上分配的空间
*heap_var = 20;
stack_var = 30;
printf("Global: %d, Heap: %d, Stack: %d\n", global_var, *heap_var, stack_var);
free(heap_var); // 释放堆空间
return 0;
}
当这个程序运行时,操作系统会为它创建一个进程,分配独立的内存空间包含代码、数据、堆和栈区域。
三、 进程的状态
Linux 进程在其生命周期中会经历不同的状态:
- 就绪:进程已准备好运行,等待 CPU 时间片。
- 运行:进程正在 CPU 上执行指令。
- 阻塞:进程因等待某个事件(如 I/O 操作完成、信号量)而暂停执行。
- 僵尸:进程已终止,但其退出状态信息还未被父进程读取。
- 停止 :进程被暂停执行(通常由信号
SIGSTOP,SIGTSTP引起)。

举例:进程状态转换

- 一个进程调用
sleep(5)会进入阻塞状态,等待 5 秒后变为就绪状态。 - 一个进程等待用户键盘输入 (
scanf) 时,会进入阻塞状态,直到用户输入数据。 - 子进程结束运行后,如果父进程没有调用
wait()或waitpid()读取其退出状态,子进程会变成僵尸进程。 - 按下
Ctrl+Z会向当前前台进程发送SIGTSTP信号,使其进入停止状态。可以使用fg或bg命令使其继续运行。
四、 进程的创建与控制
在 Linux 中,创建新进程的主要方式是 fork() 系统调用。
fork():创建一个当前进程的副本(子进程)。子进程拥有父进程代码、数据和环境的拷贝(写时复制优化)。fork()在父进程中返回子进程的 PID,在子进程中返回 0。exec()系列函数:加载一个新的程序到当前进程的内存空间,替换掉原有的代码和数据,从新程序的main函数开始执行。fork()通常与exec()配合使用来运行新程序。
举例:创建子进程并执行新程序
c
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork(); // 创建子进程
if (pid < 0) {
perror("fork failed");
return 1;
} else if (pid == 0) {
// 子进程
printf("Child process (PID: %d)\n", getpid());
// 使用 exec 加载 /bin/ls 程序
execl("/bin/ls", "ls", "-l", NULL); // 如果成功,从这里开始执行 ls -l
perror("execl failed"); // 如果 exec 失败才会执行到这里
return 1;
} else {
// 父进程
printf("Parent process (PID: %d), Child PID: %d\n", getpid(), pid);
wait(NULL); // 等待子进程结束
printf("Child process finished.\n");
}
return 0;
}
举例:避免僵尸进程
c
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
printf("Child is running...\n");
sleep(2); // 模拟子进程工作
printf("Child exiting.\n");
exit(0); // 子进程正常退出
} else {
sleep(1); // 父进程稍等片刻
int status;
pid_t child_pid = wait(&status); // 主动等待子进程结束,回收资源
if (WIFEXITED(status)) {
printf("Parent: Child %d exited with status %d.\n", child_pid, WEXITSTATUS(status));
}
}
return 0;
}
五、 进程调度
Linux 内核使用复杂的调度算法来决定哪个就绪进程获得 CPU 时间。调度策略的目标是平衡效率、响应时间和公平性。
- 普通进程 :采用完全公平调度算法,优先级由
nice值调整(范围通常为 -20 到 19,数值越低优先级越高)。使用命令nice和renice调整。 - 实时进程 :具有更高优先级,分为:
SCHED_FIFO:先进先出,一直运行直到阻塞、结束或被更高优先级抢占。SCHED_RR:轮转调度,在相同优先级的实时进程间分配时间片。- 使用命令
chrt设置实时策略和优先级。
举例:调整进程优先级
bash
# 以较高的优先级(较低的 nice 值)运行一个命令
nice -n -10 ./cpu_intensive_task
# 查看进程的 nice 值
ps -l -p $(pgrep cpu_intensive_task)
# 改变一个已运行进程的 nice 值(需要权限)
renice -n -5 -p $(pgrep cpu_intensive_task)
# 设置一个进程为 SCHED_FIFO 实时策略,优先级 99 (需要 root)
sudo chrt -f 99 ./critical_task
六、 进程间通信
独立的进程拥有各自独立的地址空间。为了实现协作,进程间需要通信机制:
- 信号 :一种异步通知机制,用于通知进程发生了某个事件。如
SIGINT(Ctrl+C),SIGKILL(强制终止)。 - 管道 :单向数据流,用于具有亲缘关系的进程(父子进程)。
|操作符就是管道。 - 命名管道:存在于文件系统中的管道,无亲缘关系的进程也可通信。
- 消息队列:内核维护的链表,进程可通过标识符发送/接收消息。
- 共享内存:多个进程访问同一块物理内存区域,速度最快,需要配合信号量等同步机制。
- 信号量:用于进程间的同步与互斥,控制对共享资源的访问。
- 套接字:可用于网络通信或同一主机上的进程间通信。
举例:使用管道通信
bash
# 父 shell 进程创建管道,将 ls 的输出传递给 grep 的输入
ls -l /etc | grep "passwd"
举例:使用信号 (发送 SIGUSR1)
bash
# 假设进程 PID 为 1234
kill -SIGUSR1 1234 # 向 PID 1234 发送 SIGUSR1 信号
总结
进程是 Linux 操作系统执行任务的核心单元。理解进程的概念、生命周期、创建方式、调度机制以及通信方法对于进行系统编程、性能分析和故障排查都至关重要。通过 ps, top, htop, pstree, strace, gdb 等工具可以观察和分析进程的行为。